快速了解汇编。 之前的节奏有点慢,换个快点的。
寄存器简介
寄存器:简单的讲是CPU中可以存储数据的器件,一个CPU中有多个寄存器。
AX 是其中一个寄存器的代号,BX 是另一个寄存器的代号。
汇编语言的组成
汇编语言由以下3类组成:
-
汇编指令 (机器码的助记符)
-
伪指令 (由编译器执行)
-
其它符号 (由编译器识别)
汇编语言的核心是汇编指令,它决定了汇编语言的特性
CPU、寄存器与内存
我们知道,CPU由控制器,运算器和寄存器组成。而内存是作为程序运行时存储数据的载体,三者的关系就好比皇帝、太监和大臣的关系。
- CPU:负责运算,指令的执行。
-
内存:狗头军师,帮助CPU存储数据,CPU通过寻址来访问内存,进行数据读取和写入。
- 寄存器:CPU与内存间频繁交互很影响效率,所以CPU有了自己的走狗——寄存器,寄存器充当的角色就是缓存。
内存中存放的内容
指令?数据?
指令和数据是应用上的概念。
在内存或磁盘上,指令和数据没有任何区别,都是二进制信息。
二进制信息:
-
1000100111011000
—>89D8H (数据)
-
1000100111011000
—>MOV AX,BX (程序)
存储单元
存储器被划分为若干个存储单元,每个存储单元从0开始顺序编号。
例如:一个存储器有128个存储单元,编号从0~127。
对于大容量的存储器一般还用以下单位来计量容量 :
- 1KB = 1024Byte
- 1MB = 1024KB
- 1GB = 1024MB
- 1TB = 1024GB
磁盘的容量单位同内存的一样,实际上以上单位是微机中常用的计量单位。
CPU对存储器的读写
存储单元从零开始顺序编号,这些编号可以看作存储单元在存储器中的地址。就像一条街,每个房子都有门牌号码。
CPU要从内存中读数据,首先要指定内存单元的地址。也就是说它要先确定它要读取哪一个存储单元中的数据。
另外,在一台微机中,不只有存储器这一种器件。CPU在读写数据时还要指明,它要对哪一个器件进行操作,进行哪种操作,是从中读出数据,还是向里面写入数据。可见,CPU要想进行数据的读写,必须和外部器件 (标准的说法是芯片) 进行下面3类信息的交互。
- 存储单元的地址 (地址信息)
- 器件的选择,读或写的命令 (控制信息)
- 读或写的数据 (数据信息)
问:那么CPU是通过什么将地址、数据和控制信息传到存储芯片中的呢?
答:电子计算机能处理、传输的信息都是电信号,电信号当然要用导线传送。
在计算机中专门有连接CPU和其他芯片的导线,通常称为总线。
- 物理上:一根根导线的集合;
- 逻辑上分为:
- 地址总线
- 数据总线
- 控制总线
总线在逻辑上划分图:
我们来分别看一下它们向内存中写入数据89D8H时,是如何通过数据总线传送数据的:
- 8088CPU:
- 8086CPU:
小结
1、汇编指令是机器指令的助记符,同机器指令 一 一对应。
2、每一种CPU都有自己的汇编指令集。
3、CPU可以直接使用的信息在存储器中存放。
4、在存储器中指令和数据没有任何区别,都是二进制信息。
5、存储单元从零开始顺序编号。
6、一个存储单元可以存储8个bit(用作单位写成”b”),即8位二进制数。 (1Byte)
7、1Byte = 8 bit 1KB = 1024Byte 1MB = 1024KB 1GB = 1024MB 1TB = 1024GB
8、每个CPU芯片都有许多管脚,这些管脚和总线相连。也可以说,这些管脚引出总线。一个CPU可以引出三种总线的宽度标志了这个CPU的不同方面的性能:
- 地址总线的宽度决定了CPU的寻址能力。
- 数据总线的宽度决定了CPU与其它器件进行数据传送时的一次数据传送量。
- 控制总线宽度决定了CPU对系统中其它器件的控制能力。
数据存储是以“字节”(Byte)为单位,数据传输是以“位”(bit)为单位,一个位就代表一个0或1(即二进制),每8个位(bit)组成一个字节(Byte)。8bit=1Byte 内存也是数据存储器的一种,所以内存里的空间也是以字节为单位的。
扩展知识
各类存储器:
- 随机存储器RAM
- 装有BIOS的ROM
- 接口卡上RAM
内存地址空间:
- 0~7FFFH的32KB为主随机存储器地址空间。
- 8000H~9FFFH的8KB为显存地址空间。
- A000~FFFFH的24KB空间为各个ROM地址空间。
解刨CPU
CPU由运算器、控制器、寄存器构成。
在CPU中:
运算器进行信息处理;寄存器进行信息存储;控制器控制各种器件进行工作。
内部总线连接各种器件,在它们之间进行数据的传送。
对汇编来讲,CPU中的主要部件是寄存器。寄存器是CPU中程序员可以用指令读写的部件。程序员通过改变各种寄存器中的内容来实现对CPU的控制。
8086CPU有14个寄存器,它们的名称为:AX, BX, CX, DX, SI, DI, SP, BP, IP, CS, SS, DS, ES, PSW(FLAG)。
F4组合:通用寄存器
AX, BX, CX, DX
F4常用于存放一般性数据,被称作通用寄存器,8086的寄存器都是16位的,没有例外。
由于历史原因,F4又可以拆分成高八位寄存器和低8位寄存器。
以 AX 为例,寄存器的逻辑结构:
eg.一个16位寄存器可以存储一个16位的数据。(数据存放情况)
数据:18
二进制表示:10010
在寄存器 AX 中的存储:
一个16位寄存器所能存储的数据的最大值为:2^16-1。
为保证兼容性,这四个寄存器都可以分为两个独立的8位寄存器使用。
- AX 可以分为 AH 和 AL; (H: 高位,L:低位)
- BX 可以分为 BH 和 BL;
- CX 可以分为 CH 和 CL;
- DX 可以分为 DH 和 DL。
以 AX 为例,8086CPU的16位寄存器分为两个8位寄存器的情况:
AX 的低8位 (0位~7位) 构成了 AL 寄存器,高8位 (8位~15位) 构成了 AH 寄存器。
AH 和 AL 寄存器是可以独立使用的8位寄存器。
8086CPU的8位寄存器数据存储情况:
一个8位寄存器所能存储的数据的最大值为:2^8-1。
几条汇编指令
CPU执行下表中的程序段的每条指令后,对寄存器中的数据进行的改变:
ax = 044CH (1044CH,这里1无法存放了,细节之后讲)
ax = 0158H
指令操作对象的一致性:
物理地址
CPU访问内存单元时要给出内存单元的地址。所有的内存单元构成的存储空间是一个一维的线性空间。
我们将这个唯一的地址称为物理地址。
CPU通过地址总线送入存储器的,必须是一个内存单元的物理地址。在CPU向地址总线上发出物理地址之气,必须要在内部先形成这个物理地址。不同的CPU可以有不同的形成物理地址的方式。
16位结构的CPU
概括的讲,16位结构描述了一个CPU具有以下几个方面特征:
- 运算器一次最多可以处理16位的数据。
- 寄存器的最大宽度为16位。
- 寄存器和运算器之间的通路是16位的。
8086CPU给出物理地址的方法
- 8086有20位地址总线,可传送20位地址,寻址能力为1M。
- 8086内部为16位结构,它只能传送16位的地址,表现出的寻址能力却只有64K。
8086CPU采用一种在内部用两个16位地址合成的方法来形成一个20位的物理地址:
地址加法器合成物理地址的方法:
物理地址 = 段地址 + 偏移地址
“段地址 * 16” 有一个更为常用的说法就是数据左移4位。(二进制位)
我们通过观察位移次数和各种形式数据的关系:
1、一个数据的二进制形式左移1位,相当于该数据乘以2;
2、一个数据的二进制形式左移N位,相当于该数据乘以2的N次方;
3、地址加法器如何完成段地址 * 16的运算?
以二进制形式存放段地址左移4位。
经过进一步思考,我们可以得出:
1、一个数据的16进制形式左移1位,相当于乘以16;
2、一个数据的10进制形式左移1位,相当于乘以10;
3、一个数据的X进制形式左移1位,相当于乘以X。
“段地址*16 + 偏移地址 = 物理地址”的本质含义
“基础地址 + 偏移地址 = 物理地址” 比喻 ===>
“段地址*16 + 偏移地址 = 物理地址” 比喻 ===>
觉得不怎么样,所以就不说了,阿巴阿巴阿巴……
段的概念
错误认识:
内存被划分成了一个一个的段,每一个段有一个段地址。
其实:
内存并没有分段,段的划分来自于CPU,由于8086CPU用 “(段地址*16) + 偏移地址 = 物理地址” 的方式给出内存单元的物理地址,使得我们可以用分段的方式来管理内存。
以后,在编程时可以根据需要,将若干地址连续的内存单元看作一个段,用段地址*16定位段的起始地址 (基础地址),用偏移地址定段中的内存单元。
两点需要注意:
1、段地址*16必然是16的倍数,所以一个段的起始地址也一定是16的倍数。
2、偏移地址为16位,16位地址的寻址能力为64K,所以一个段的长度最大为64K。
CS:IP
段地址由段寄存器指定。
8086有四个段寄存器:CS、DS、SS、ES。
CS为代码段寄存器,IP为指令指针寄存器,它们指示了CPU当前要读取指令的地址。8086CPU将CS:IP指向的内容当做当前指令执行。
说明书:
1、8086CPU当前状态:CS中的内容为2000H,IP内容为0000H。
2、内存20000H~20009H单元中存放着可执行的机器码。
3、内存20000H~20009H单元中存放的机器码对应的汇编指令如下。
工作过程:
1、从CS:IP指向的内存单元读取指令,读取的指令进入指令缓冲器。
2、IP=IP+所读取指令的长度,从而指向下一条指令。
3、指向指令,转到1,重复过程。
jmp
一个忧桑的故事:
很多时候,我们想改变CS和IP的值,但是又无奈CS、IP是VIP 白金会员,根本不屌mov,于是jmp就像他的名字一样,第一 个跳出来,专治各种不服 (什么,你问我凭什么不能用mov, 我就抢你,还凭什么)。
jmp 2AE3:3; 执行后:CS=2AE3H, IP=0003H,CPU从2AE33H处读指令。
jmp 3:0B16; 执行后:CS=0003H, IP=0B16H,CPU从00B46H处读指令。
jmp ax; 执行前 ax=1000H, CS=2000H, IP=0003H
执行后 ax=1000H, CS=2000H, IP=1000H
检测
1、给定段地址为0001H,仅通过变化偏移地址寻址,CPU的寻址范围为: (00010H~1000FH)。
解:偏移地址为16位,变化范围为0~FFFFH。
物理地址=段地址(SA)*16 + 偏移地址(EA)
=0001H*10H + 0
=00010H
物理地址=段地址(SA)*16 + 偏移地址(EA)
=0001H*10H + FFFFH
=1000FH
2、有一数据存放在内存20000H单元中,现给定段地址为 SA,若想用偏移地址寻到此单元。则SA应满足的条件是:最小为(1001H),最大为(2000H)。
解:偏移地址为16位,变化范围为 0~FFFFH。
物理地址=段地址(SA)*16 + 偏移地址(EA)
20000H = SA * 16 + EA
SA=(20000H-EA)/16
EA 取最大值时,SA得最小值,SA=2000H-FFFFH/16=1001H
EA 取最小值时,SA得最大值,SA=2000H-0H/16=2000H
3、如图:
mov ax, 6622H (ax=6622H)
jmp 1000:3 (CS=1000H, IP=3H)
mov ax, 0000 (ax=0000H, CS=1000H)
mov bx, ax (bx=0000H, CS=1000H, IP=0000H)
jmp bx (bx=0000H, CS=1000H, IP=0000H)
mov ax, 0123H (ax=0123H, CS=1000H, IP=0000H)
mov ax, 0000H
…..循环去了
4、下面的3条指令执行后,CPU几次修改IP?都在什么时候?最后IP的值是多少? mov ax,bx sub ax,ax jmp ax
CPU四次修改IP。
第1次:从存储器中读取mov ax, bx指令之后,IP立即改变,指向下一条指令(sub ax, ax)的地址。 第2次:从存储器读取sub ax, ax之后(在此之前,会先执行mov ax, bx指令),IP立即改变,指向下一条指令(jmp ax)的地址。 第3次:从存储器读取jmp ax指令之后(在此之前,会先执行完sub ax, ax指令),IP立即改变,指向下一条指令的地址,这里由于下一条指令没有标明,所以不知道,反正此时IP的值是紧挨jmp ax之后的地址。 第4次:执行jmp ax指令后。jmp指令是通过修改IP的值来达到使程序执行跳转的目的的,因此执行jmp之后,IP的值变为ax的值。
字与字节
CPU中用16位来存储一个字,高八位放高位字节,低八位放低位字节。
由于内存单元是字节单元,所以一个字要用两个地址连续的内存单元存放,低位字节放在低地址单元中,高位字节放在高地址单元中。
我们提出子单元的概念:字单元。以后还会接触双字,甚至四字单元。
DS:[address]
8086CPU还有一个DS,这个DS用于存放要访问数据的段地址。
mov bx, 1000H
mov ds, bx
mov al, [0]
说明:将10000H(1000H:0H)数据读取到al中。
你认识mov,认识al,更认识0,不过可能不认识[],这个就是指偏移地址是0处的内容,但是或许你会问,段地址有很多,如何搞清楚这是哪一个,那么请记住,一般形式是 寄存器:[XXX],如果缺省,则默认为DS段,所以上面的指令相当于把DS:[0]处的内容放入al中。
注意: DS 不能直接用 立即数赋值,要用通用寄存器赋值。
mov bx, 1000H
mov ds, bx √
mov ds, 1000H ×
数据总线宽度的真谛
因为8086CPU是16位的结构,有16根数据线,所以可以一次性传送16位的数据,也就是说可以一次性传送一个字。只要在mov指令中给出16位的寄存器就可以进行16位数据的传送。
如:
mov bx, 1000H
mov ds, bx
mov ax, [0] //1000H:0H处的字形数据传送入ax
mov [0], cx //cx中的16位数据送到1000H:0H处
mov add sub
mov 寄存器,段寄存器 //可以
mov 段寄存器,内存单元 //不可以
sub 和 add 基本一致。
栈
栈本身是一种数据结构,我们这里用到的栈其实是一段内存空间,只是比较特殊,有特殊的访问形式,达到不可思议的效果。
先进后出
push 和 pop
如今的CPU都有栈的设计,编程时,我们可以将一段内存当作栈来使用。
8086CPU提供入栈和出栈指令,最基本的两个是push(入栈)和pop(出栈)。如:push ax 表示将寄存器ax中的数据放入栈中,pop ax 则表示将栈顶中数据取出放入ax中。
注:8086CPU入栈和出栈都是以字为单位进行的。
看图理解:
由上引发的两个问题
- CPU如何知道 10000H~1000FH 这段空间被当作栈来使用。
- push,pop 在执行的时候,必须知道哪个单元是栈顶单元,可是,如何知道呢?
实际上,就像代码段有CS 和 IP 一样,栈也有自己的段,也有自己的 SS 和 SP。
任意时刻,SS:SP 指向栈顶元素!
push pop 详解
push ax
1、sp=sp-2,ss:sp 指向当前栈顶前面的单元,以当前栈顶前面的单元为新的栈顶。
2、将ax中的内容送入到ss:sp指向的内存单元中,ss:sp此时指向新的栈顶。
pop ax
1、ss:sp中的数据送入ax
2、sp += 2
看图说话:
检测
1、
AX=2662 BX=E626 AX=E626 AX=2662 BX=D6E6 AX=FD48 AX=2C14 AX=0 AX=00E6 BX=0 BX=0026 AX=000C
2、内存的情况如图所示。
各寄存器的初始值:CS=2000H, IP=0, DS=1000H, AX=0, BX=0;
(1)写出CPU指行的指令序列 (用汇编写出)。
(2)写出CPU执行每条指令后,CS, IP和相关寄存器中的数值。
(2)再次体会:数据和程序有区别吗,如何确定内存中的信息哪些是数据,哪些是程序。
解:
指令 | CS | IP | DS | AX | BX |
---|---|---|---|---|---|
mov ax, 6622H | 2000H | 0+3 | 6626H | ||
jmp 0ff0:0100 | 0FF0H | 0100 | |||
mov ax, 2000H | 0FF0H | 0100+3 | 2000H | ||
mov ds, ax | 0FF0H | 0103+2 | 2000H | ||
mov ax, [0008] | 0FF0H | 0105+3 | C389 | ||
mov ax, [0002] | 0FF0H | 0108+3 | EA66 |
没有区别。CS和IP指示了当前要读取的程序的地址,DS指向的是数据段。
3、(1) 补全下面程序,使其可以将10000H~1000FH中的8个字,逆序复制到20000H~2000FH中。逆序复制的含义如图所示(图中内存中的数据均为假设)。
mov ax,1000H
mov ds,ax
mov ax,2000H ;因为立即数不能直接送入段寄存器,需要通用寄存器中转一下
mov ss,ax ;SS存放的是栈的段地址
mov sp,10H ;SP存放的是栈的段地址SS:SP指向 20010H(即2000FH的下一个地址)
push [0] ;把0123 复制到2000E 2000F, 2000E=23 ,2000F=01
push [2] ;把2266 复制到2000C 2000D 2000C=66 2000D=22
push [4]
push [6]
push [8]
push [A]
push [C]
push [E]
(2)补全下面程序,使其可以将10000H~1000FH中的8个字,逆序复制到20000H~2000FH中。
mov ax,2000H
mov ds,ax
mov ax,1000H
mov ss,ax
mov sp,0
pop [E] ;把 ss:sp 10000H 弹入到 ds:[e] 2000EH
pop [C] ;把 ss:sp 10002H 弹入到 ds:[c] 2000CH
pop [A]
pop [8]
pop [6]
pop [4]
pop [2]
pop [0]