CPU组成:
- 运算器
- 控制器
- 寄存器
- ······
这些器件靠内部总线相连。
区别:
- 内部总线实现CPU内部各个器件之间的联系。
- 外部总线实现CPU和主板上其它器件的联系
8086CPU所有的寄存器都是16位的,可以存放两个字节。
AX, BX, CX, DX通常用来存放一般性的数据被称为通用寄存器。
一个16位寄存器所能存储的最大值为2^16-1。
- AX可以分为:AH(高)和AL(低)
- BX可以分为:BH和BL
- CX可以分为:CH和CL
- DX可以分为:DH和DL
AX的低8位(0~7位)构成了AL寄存器,
高8位(8~15位)构成了AH寄存器。
AH和AL寄存器是可以独立使用的8位寄存器。
字在寄存器中的存储
- 字节:记为byte,一个字节由8个bit组成,可以存在8位寄存器中。
- 字:记为word,一个字由两个字节组成,这两个字节分别称为这个字的高位字节和低位字节
一个字可以存在一个16位寄存器中,这个字的高位字节和低位字节自然就存在这个寄存器的高8位寄存器和低8位寄存器中。
汇编指令不区分大小写。
看一些汇编指令的案例:
看一个例题:
但是一个寄存器只能存放16的数字,1044C超过了16位。
所以答案是044C,1并没有被抛弃,而是放到了一个进制位中去。
再看一个案例:
这里将AH和AL看成是两个不同的寄存器,运算也是分开的。
所以答案是:0058H
这里的丢失:指的是进位制不能在8位寄存器中保存,但是CPU不是真的丢弃这个进位值。
物理地址
CPU访问内存单元时要给出内存单元的地址。所有的内存单元构成的存储空间是一个一维的线性空间。
我们将这个唯一的地址称为物理地址。
16位结构的CPU
16位结构描述了一个CPU具有以下几个方面的特征:
- 运算器一次最多可以处理16位的数据(64位可以处理64位的数据)
- 寄存器的最大宽度为16位(8个字节)(64位最大宽度64位)
- 寄存器和运算器之间的通路是16位的。
- 8086有20位地址总线,可传送20位地址,寻址能力为1M。
- 8086内部为16位结构,只能传送16位地址。表现出的寻址能力只有64K
8086CPU给出物理地址的方法:
8086CPU采用一种在内部用两个16位地址合成的方法来形成一个20位的物理地址。
地址加法器合成物理地址的方法:
物理地址 = 段地址 x 16 + 偏移地址
1230就是段地址,16进制的数 *16 就是在后面加上一个0(左移一位)。
段地址 x 16 就是数据左移4位(二进制位)
- 一个数据的二进制形式左移1位,相当于该数据乘以2.
- 个数据的二进制形式左移n位,相当于该数据乘以2^n.
- 一个n进制数左移m位,相当于该数据乘以n^m.
段
错误认识:内存被划分成了一个一个的段,每一个段有一个段地址。
正确认识: 内存并没有分段,段的划分来自于CPU,由于8086CPU用“ (段地址 x 16) + 偏移地址 = 物理地址; ” 的方式给出内存单元的物理地址,使得我们可以用分段的方式来管理内存。
段只是我们自己强加的概念。
在编程时,可以根据需要,将若干地址连续的内存单元看作一个段,用 段地址 x 16 定位段的起始地址(基础地址),用偏移地址定位段中的内存单元
注意:
- 段地址 x 16 必然是16倍数,所以一个段的起始地址也一定是16的倍数。
- 偏移地址为16位,16位的地址寻址能力为64k,所以一个段的长度最大为64k。
小结:
- CPU访问内存单元时,必须向内存提供内存单元的物理地址。
- 8086CPU在内部用段地址和偏移地址移位相加的方法形成最终的物理地址。
寻址能力就是寻找地址的最大范围。
思考:
- 观察下面的图片,可以得到什么结论?
结论:**CPU可以用不同的段地址和便宜地址形成同一个物理地址。**
-
如果给定一个段地址,仅通过变化偏移地址来进行寻址,最多可以定位多少内存单元?
结论:偏移地址16位,变化范围位 0 ~ FFFFH,仅用偏移地址来寻址最多可寻64K个内存单元。
8086PC机种,存储单元的地址用两个元素来描述,即段地址和偏移地址。
可根据需要,将地址连续,起始地址为16的倍数的一组内存单元定义为一个**段**。
段寄存器
AX, BX, CX, DX通常用来存放一般性的数据被称为通用寄存器。
段寄存器就是提供段地址的。
8086CPU有4个段寄存器:
- CS 代码段寄存器
- DS 数据段寄存器
- SS 堆栈段寄存器
- ES 附加段寄存器
8086CPU要访问内存时,由这4个段寄存器提供内存单元的段地址。
CS IP
CS和IP是8086CPU中最关键的寄存器,它们指示了CPU当前要读取指令的地址。
CS为代码段寄存器
IP为指令指针寄存器
8086CPU工作过程的简要描述:
- 从CS:IP指向内存单元读取指令,读取的指令进入指令缓冲区。
- IP= IP + 所读取指令得长度,从而指向下一条指令。
- 执行指令。转到步骤1,重复这个过程。
CS和IP
- 在任何时候,CPU将CS,IP中的内容但顾总指令的段地址和偏移地址,用它们合成指令的物理地址,到内存中读取指令码,执行。
- 如果内存中的一段信息曾被CPU执行过的话,那么它所在的内存单元必然被CS:IP指向过。
修改CS,IP的指令。
- 在CPU中,程序员能用指令读写的部件只有寄存器,程序员可以通过改变寄存器中的内容实现对CPU的控制。
- CPU从何处执行指令是由CS,IP中的内容决定的,程序员可以通过改变CS,IP中的内容来控制CPU执行目标指令。
如何修改寄存器的值?
修改AX中的值:
-
mov指令
如
mov ax, 123
mov指令可以改变8086CPU大部分寄存器的值,被称为传送指令
但是我们不能通过mov改变CS,IP的值,8080CPU没有提供这样的功能。
在8086CPU中提供了转移指令来修改CS,IP的值。
- jmp 段地址 : 偏移地址
jmp 2AE3 : 3
功能:用指令中给出的段地址修改CS,偏移地址修改IPjmp 3 : 0B16
-
仅修改IP的内容:
jmp 某一合法寄存器
jmp ax (类似于mov IP, ax)
功能:用寄存器中的值修改IPjmp bx
CPU的运行流程:
内存中存放的机器码和对应汇编指令情况:
初始:CS = 2000H,IP = 0000H
运行流程:
代码段
对于8086PC机,在编程时,可以根据需要,将一组内存单元定义为一个段。
可以将长度为N(N<=64KB)的一组代码,存在一组地址连续,起始地址为16的倍数的内存单元中,这段内存是用来存放代码的,从而定义了一个 代码段。
如何使得代码段中的指令被执行呢?
- 将一段内存当作代码段,仅仅是我们在编程时的一种安排。
- CPU并不会由于这种安排就自动的将我们定义的代码段中的指令当作指令来执行。
- CPU只认被CS:IP指向的内存单元中的内容为指令
- 要将CS:IP指向所定义的代码段中的第一条指令的首地址
小结:
- 段地址在8086CPU的寄存器中存放。当8086CPU要访问内存时,由段寄存器提供内存单元的段地址,8086CPU有4个段寄存器,其中CS用来存放指令的段地址。
-
CS存放指令的段地址,IP存放指令的偏移地址
8086机中,任意时刻,CPU将CS : IP 指向的内容当作指令执行
-
8086CPU的工作过程:
⓵ 从CS:IP指向内存单元读取指令,读取的指令进入指令缓冲 器。
⓶ IP指向下一条指令(IP = IP + 执行指令的长度)
⓷ 执行指令。
⓸ 转到第一个步骤,重复这个过程。
- 8086CPU提供转移指令修改CS,IP的内容。
DEBUG
百度网盘下载DOSBOX链接:
链接:https://pan.baidu.com/s/1yohrvZWi6pDWN91D2NprHg
提取码:fpf7
debug.exe下载地址:
链接:https://pan.baidu.com/s/1b6wUW5XTffLv9IpT_SVTBA
提取码:cyvz
输入
mount c c:\
回车(挂载到c盘)
输入
c:\
回车(跳转到c盘)
输入
debug
- R 查看,改变CPU寄存器的内容 注意CS,IP的值,
即内存CS = 0CA2, IP = 0100,
0CA2 : 0100
处的指令为CPU当前要读取执行的指令。
还可以看到CS : IP 存放的机器码为 0000, 对应的汇编指令为
若要修改一个寄存器的值,在ADD [BX+SI],AL
命令后加寄存器名后按Enter,出现"R
",输入要写入的数据,即可完成修改。:
-
D 查看内存中的内容
使用
d 段地址 : 偏移地址
- E 命令改写内存中的内容
- U 将内存中的机器指令翻译成汇编指令
-
T 执行一条机器指令
每执行一次t命令,就是执行一次
指向的内存单元CS:IP
-
A 以汇编指令的格式在内存中写入一条机器指令
之前的
等指令可以在a命令后执行:mov ax, 1234
小测试
- 将下面3条指令写入从 2000 : 0 开始的内存单元中,利用这三条指令计算2的8次方。
输入下面的命令: 输入t命令,观察ax中的内容,会发现每执行一次t命令,ax就会逐渐变化 最后的结果是:mov ax, 1 add ax, ax jmp 2000 : 0003
-
查看内存中的内容
PC机主板上的ROM中写有一个生产日期,在内存的FFF00H~FFFFFH的某几个单元中,请找到这个生产日期并试图改变它。
d fff0:0 ff e ffff:05 e ffff:06 d fff0:0 ff
-
向内存从B8100H开始的单元中填写数据,如:
先填写不同的数据,观察产生的现象;再改变填写的地址,观察产生的现象。
输入上面的指令,会发现右上角会有😊❤♦
b810:0 其实就是显卡显存的地址。
在上面写上数据会在屏幕上显示出来。我随便写点数据:
会显示其它不同的现象。
内存访问
下面的内容中,我们从访问内存的角度继续学习几个寄存器。
内存中字的存储
在0地址处开始存放20000(4E20H)
注意:0号单元是低地址单元,1号单元是高地址单元。
问题:
-
0地址单元中存放的字节型数据是多少?
20H
-
0地址单元中存放的字型数据是多少?
4E20H
-
2地址单元中存放的字节型数据是多少?
12H
-
2地址单元中存放的字型数据是多少?
0012H
-
1地址单元中存放的字型数据是多少?
124EH
结论:任何两个地址连续的内存单元,N号单元和N+1号单元,可以将它们看成两个内存单元,也可看成一个地址为N的字单元中的高位字节单元和低位字节单元。
DS和[ address ]
- CPU要读取一个内存单元时,必须先给出这个内存单元的地址;
- 8086CPU中,内存地址由
组成段地址 + 偏移地址
- 8086CPU中有一个DS寄存器,通常用来存放要访问的数据的段地址。
例如,我们要读取1000H单元的内容可以用如下程序段进行:
mov bx, 1000H
mov ds, bx
mov al, [0]
上面三条指令将1000H(1000:0)中的数据读到al中。
分析:
-
mov al, [0]
mov指令可将一个内存单元中的内容送入一个寄存器
从哪个内存单元送到哪个寄存器中呢?
mov 寄存器名, 内存单元地址
[···] 表示一个内存单元,[···] 中的0表示内存单元中的偏移地址。
[0]表示偏移地址为0
-
内存单元中的段地址是多少呢?
执行指令时,8086CPU自动取DS中的数据为内存单元的段地址。
- 如何用
指令从mov
中读取数据?1000H
-
表示1000H
(段地址 : 偏移地址)1000:0
- 将段地址1000H放入ds
- 用
mov al, [0]
完成传送
mov指令中的
说明操作对象是一个内存单元,[]
中的0说明这个内存单元的偏移地址是0,它的段地址默认放在DS中。[]
-
- 为何不直接
?mov ds 1000H
- 传送指令
mov ax, 1
- 相似的方式
可不可以?mov ds, 1000H
-
是不可以的,8086CPU不支持将数据直接送入段寄存器的操作,ds是一个段寄存器。mov ds, 1000H
- 但是可以将数据直接送入通用寄存器(AX,BX,CX,DX)
- 数据→通用寄存器→段寄存器
- 传送指令
已知mov指令可完成三种传送功能:
- 将数据直接送入寄存器;
- 将一个寄存器中的内容送入另一个寄存器中;
- 将一个内存单元中的内容送入一个寄存器
问题:将al中的数据送入内存单元
1000H
。
mov bx, 1000H
: 将数据从内存单元送入寄存器。
mov bx, 1000H
mov ds, bx
mov [0], al
字的传送
8086CPU是16位结构,有16根数据线,所以可以一次性传送16位的数据,也就是一次性传送一个字。
比如:
mov bx, 1000H
mov ds, bx ;确定送入的段地址
mov ax, [0] ;1000:0处的字型数据送入ax
mov [0], cx ;cx中的16位数据送到1000:0处
问题1:内存中的情况如下图,写出下面指令执行后寄存器ax, bx, cx 中的值。
分析:
mov ax, 1000H
,
mov ds, ax
确定以送入的段地址位1000H,
mov ax, [0]
: 将偏移地址为0即23送入ax,
mov bx, [2]
: 将22送入bx
mov cx, [1]
: 将11送入cx
add bx, [1]
: 将11+22 = 33
add cx, [2]
: 22 + 11 = 33
首先将23,11,22,66存入内存:
查看CS:IP指向的数据:
-r
-d CS:IP
发现CS:IP指向的数据几乎全为0,说明应该是不重要的数据,可以随意覆盖。
执行指令:
执行7次T指令后,可以发现ax,bx,cx的值。
问题2:内存中的情况如下图,写出下面指令执行后寄存器ax, bx, cx中的值。
-e 1000:0 23 11 22 11
-d 1000:0
-r
-a CS:IP
-mov ax,1000H
-mov ds,ax
-mov ax 2C34 ;11316的16进制是2C34
-······
-t
-t
-t
-t
-t
-t
-t
mov,add,sub指令
已学mov指令的几种形式
- mov 寄存器,数据
mov ax,6
- mov 寄存器,寄存器
mov ax,bx
- mov 寄存器,内存单元
mov ax,[偏移地址]
- mov 内存单元,寄存器
mov [偏移地址],ax
- mov 段寄存器,寄存器
mov ds,ax
- mov 寄存器,段寄存器
mov ax,ds
add,sub指令同mov指令,都有两个操作对象。
但add和sub指令无法对段寄存器进行操作。
数据段
对于8086PC机,我们可以根据需要将一组内存单元定义为一个段。
同样一段代码,我们用CS指向它,它就是是代码段,用DS指向它,它就是数据段。
我们可以将一组长度为N(N<=64K),地址连续,起始地址为16的倍数的内存单元当作专门存储数据的内存空间,从而定义了一个数据段。
我们用123B0H~123B9H这段空间来存放数据:
- 段地址:123BH
- 长度:10字节
访问数据段中的数据:
将一段内存当作数据段,是我们在编程时的一种安排,我们可以在具体操作的时候,用ds存放数据段的段地址,再根据需要,用相关指令访问数据段中的具体单元。
累加数据段的前3个字型数据:
mov ax,123B
mov ds,ax
mov ax,0
add ax[0]
add ax[2]
add ax[4]
- 字在内存中存储时,要用两个地址连续的内存单元来存放,字的低位字节存放在低地址单元中,高位字节存放再高地址单元中。
- 用mov指令姚访问内存单元,可以在mov指令中只给出单元的偏移地址,此时,段地址默认在DS寄存器中。
- [address]表示一个偏移地址为address的内存单元。
- 在内存和寄存器之间传送字型数据时,高地址单元和高8位寄存器,低地址单元和低8位寄存器相对应。
- mov,add,sub是具有两个操作对象的指令。
- jmp是具有一个操作对象的指令。
栈
栈式一种先进后出(LIFO)的结构。
- 入栈 : 将一个新的元素放到栈顶;
- 出栈 : 从栈顶取出一个元素。
栈也是内存空间的一部分,只是一段可以以一种特殊的方式进行访问的内存。
我们在基于8086CPU编程的时候,可以将一段内存当作栈来使用。
8086CPU提供入栈和出栈指令:
- PUSH 入栈
: 将寄存器AX中的数据送入栈中PUSH AX
- POP 出栈
: 从栈顶取出数据送入AXPOP AX
8086CPU的入栈和出栈操作都是以字为单位进行的。
注意:字型数据用两个单元存放,高地址单元放高8位,低地址单元放低8位。
疑问
- CPU如何知道一段内存空间被当作栈使用?
- 执行PUSH和POP,如何知道哪个单元是栈顶单元?
分析
CPU如何知道当前要执行的指令所在的位置?
答: 寄存器CS和IP中存放着当前指令的段地址和偏移地址。
8086CPU中,有两个寄存器:
- 段寄存器SS 存放栈顶的段地址
- 寄存器SP 存放栈顶的偏移地址
任意时刻,SS : SP 指向23当前栈顶元素。
PUSH AX
- SP = SP - 2;
- 将AX中的内容送入SS : SP指向的内存单元处,SS : SP此时指向新栈顶。
- 任意时刻,SS : SP 指向栈顶元素,当栈为空时,栈中没有元素,也就不存在栈顶元素。
- 所以SS : SP 只能指向栈的最底部单元下面的单元,该单元的偏移地址位栈最底部的字单元的偏移地址+2
- 栈最底部字单元的地址位1000:000E,所以栈空时,SP = 0010H.
POP AX
- 将SS : SP 指向的内存单元处的数据送入AX中;
- SP = SP + 2, SS : SP指向当前栈顶下面的单元,以当前栈顶下面的单元位新的栈顶。
注意
- 出栈后,SS : SP 指向新的栈顶1000EH,pop操作前的栈顶元素,1000CH处的2266H依然存在,但是已经不在栈中。
- 当再次执行push等入栈指令后,SS : SP移至1000CH,并在里面写入新的数据,它将被覆盖。
PUSH和POP指令可以在寄存器和内存之间传送数据
栈顶超界
- SS 和 SP只记录了栈顶的地址,依靠SS和SP可以保证在入栈和出栈是找到栈顶。
入栈出栈时,如果栈顶超界,可能会造成比较严重的后果。
因为栈空间之外的空间可能存放了具有其它用途的数据,代码等,这些数据,代码可能是我们自己的程序中的,也可能是别的程序中的。
8086CPU工作时,只考虑当前的情况:
- 当前栈顶在何处;
- 当前要执行的指令是哪一条。
PUSH,POP指令
编程1:
将10000H~1000FH这段空间当作栈,初始状态是空的,将AX,BX,DS中的数据入栈:
mov ax,1000H
mov ss,ax
mov sp,0010H
push ax
push bx
push ds
编程2:
(1)将10000H~1000FH这段空间当作栈,初始状态为空;
(2)设置AX = 001AH, BX = 001BH;
(3)将AX,BX中的数据入栈
(4)将AX,BX清零
(5)从栈中恢复AX,BX
mov ax,1000
mov ss,ax
mov sp,0010 ;偏移地址
mov ax,001A
mov bx,001b
push ax
push bx
sub ax,ax ;也可用mov ax,0
sub bx,bx ;也可用mov bx,0
pop bx
pop ax
编程3:
(1)将10000H~1000FH这段空间当作栈,初始状态为空;
(2)设置AX = 002H,BX = 002BH;
(3)利用栈,交换AX和BX的值。
mov ax,1000
mov ss,ax
mov sp,0010
mov ax,001A
mov bx,001B
push ax
push bx
pop ax
pop bx
push,pop实质上就是一种内存传送指令,可以在寄存器和内存之间传送数据,与mov指令不同的是,push和pop指令访问的内存单元的地址不是在指令中给出的,而是由SS:SP指出的.
同时push和pop也会改变sp中的内容。
- 执行push:先改变SP,后向SS : SP处传送
- 执行pop :先读取SS : SP处的数据,后改变SP
push,pop等栈操作指令,修改的只是SP。也就是说是,栈顶的最大变化范围是:0~FFFFH
栈段
我们可以将长度位N(N<=64K)的一组地址连续,起始地址为16倍数的内存单元,当作栈使用,从而定义了一个栈段。