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
高8位(8~15位)构成了AH寄存器。
AH和AL寄存器是可以独立使用的8位寄存器。
一个字可以存在一个16位寄存器中,这个字的高位字节和低位字节自然就存在这个寄存器的高8位寄存器和低8位寄存器中。
汇编指令不区分大小写。
看一个例题:
但是一个寄存器只能存放16的数字,1044C超过了16位。
所以答案是044C,1并没有被抛弃,而是放到了一个进制位中去。
再看一个案例:
这里将AH和AL看成是两个不同的寄存器,运算也是分开的。
所以答案是:0058H
这里的丢失:指的是进位制不能在8位寄存器中保存,但是CPU不是真的丢弃这个进位值。
CPU访问内存单元时要给出内存单元的地址。所有的内存单元构成的存储空间是一个一维的线性空间。
我们将这个唯一的地址称为物理地址。
16位结构描述了一个CPU具有以下几个方面的特征:
8086CPU给出物理地址的方法:
8086CPU采用一种在内部用两个16位地址合成的方法来形成一个20位的物理地址。
地址加法器合成物理地址的方法:
物理地址 = 段地址 x 16 + 偏移地址
1230就是段地址,16进制的数 *16 就是在后面加上一个0(左移一位)。
段地址 x 16 就是数据左移4位(二进制位)
基础地址 + 偏移地址 = 物理地址;
段地址 x 16 + 偏移地址 = 物理地址;
错误认识:内存被划分成了一个一个的段,每一个段有一个段地址。
正确认识: 内存并没有分段,段的划分来自于CPU,由于8086CPU用“ (段地址 x 16) + 偏移地址 = 物理地址; ” 的方式给出内存单元的物理地址,使得我们可以用分段的方式来管理内存。
段只是我们自己强加的概念。
在编程时,可以根据需要,将若干地址连续的内存单元看作一个段,用 段地址 x 16 定位段的起始地址(基础地址),用偏移地址定位段中的内存单元
注意:
小结:
寻址能力就是寻找地址的最大范围。
思考:
结论:**CPU可以用不同的段地址和便宜地址形成同一个物理地址。**
如果给定一个段地址,仅通过变化偏移地址来进行寻址,最多可以定位多少内存单元?
结论:偏移地址16位,变化范围位 0 ~ FFFFH,仅用偏移地址来寻址最多可寻64K个内存单元。
8086PC机种,存储单元的地址用两个元素来描述,即段地址和偏移地址。
物理地址 = 段地址 x 16 + 偏移地址;
可根据需要,将地址连续,起始地址为16的倍数的一组内存单元定义为一个**段**。
AX, BX, CX, DX通常用来存放一般性的数据被称为通用寄存器。
段寄存器就是提供段地址的。
8086CPU有4个段寄存器:
8086CPU要访问内存时,由这4个段寄存器提供内存单元的段地址。
CS和IP是8086CPU中最关键的寄存器,它们指示了CPU当前要读取指令的地址。
CS为代码段寄存器
IP为指令指针寄存器
8086CPU工作过程的简要描述:
CS和IP
修改CS,IP的指令。
如何修改寄存器的值?
修改AX中的值:
mov指令
如mov ax, 123
mov指令可以改变8086CPU大部分寄存器的值,被称为传送指令
但是我们不能通过mov改变CS,IP的值,8080CPU没有提供这样的功能。
在8086CPU中提供了转移指令来修改CS,IP的值。
jmp 段地址 : 偏移地址
jmp 2AE3 : 3
jmp 3 : 0B16
功能:用指令中给出的段地址修改CS,偏移地址修改IP
仅修改IP的内容:
jmp 某一合法寄存器
jmp ax (类似于mov IP, ax)
jmp bx
功能:用寄存器中的值修改IP
CPU的运行流程:
内存中存放的机器码和对应汇编指令情况:
对于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的内容。
百度网盘下载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
注意CS,IP的值,CS = 0CA2, IP = 0100,
即内存0CA2 : 0100
处的指令为CPU当前要读取执行的指令。
还可以看到CS : IP 存放的机器码为 0000, 对应的汇编指令为ADD [BX+SI],AL
若要修改一个寄存器的值,在R
命令后加寄存器名后按Enter,出现":
",输入要写入的数据,即可完成修改。
D 查看内存中的内容
使用 d 段地址 : 偏移地址
E 命令改写内存中的内容
U 将内存中的机器指令翻译成汇编指令
T 执行一条机器指令
每执行一次t命令,就是执行一次CS:IP
指向的内存单元
将下面3条指令写入从 2000 : 0 开始的内存单元中,利用这三条指令计算2的8次方。
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开始的单元中填写数据,如:
-e B810:0000 01 01 02 02 03 03 04 04
先填写不同的数据,观察产生的现象;再改变填写的地址,观察产生的现象。
b810:0 其实就是显卡显存的地址。
下面的内容中,我们从访问内存的角度继续学习几个寄存器。
在0地址处开始存放20000(4E20H)
注意:0号单元是低地址单元,1号单元是高地址单元。
问题:
0地址单元中存放的字节型数据是多少?
20H
0地址单元中存放的字型数据是多少?
4E20H
2地址单元中存放的字节型数据是多少?
12H
2地址单元中存放的字型数据是多少?
0012H
1地址单元中存放的字型数据是多少?
124EH
结论:任何两个地址连续的内存单元,N号单元和N+1号单元,可以将它们看成两个内存单元,也可看成一个地址为N的字单元中的高位字节单元和低位字节单元。
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
可不可以?mov ds, 1000H
是不可以的,8086CPU不支持将数据直接送入段寄存器的操作,ds是一个段寄存器。已知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存入内存:
-e 1000:0 23 11 22 66
查看CS:IP指向的数据:
-r
-d CS:IP
发现CS:IP指向的数据几乎全为0,说明应该是不重要的数据,可以随意覆盖。
执行指令:
问题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指令的几种形式
mov ax,6
mov ax,bx
mov ax,[偏移地址]
mov [偏移地址],ax
mov ds,ax
mov ax,ds
add,sub指令同mov指令,都有两个操作对象。
但add和sub指令无法对段寄存器进行操作。
对于8086PC机,我们可以根据需要将一组内存单元定义为一个段。
同样一段代码,我们用CS指向它,它就是是代码段,用DS指向它,它就是数据段。
我们可以将一组长度为N(N<=64K),地址连续,起始地址为16的倍数的内存单元当作专门存储数据的内存空间,从而定义了一个数据段。
我们用123B0H~123B9H这段空间来存放数据:
访问数据段中的数据:
将一段内存当作数据段,是我们在编程时的一种安排,我们可以在具体操作的时候,用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 入栈
PUSH AX
: 将寄存器AX中的数据送入栈中
POP 出栈
POP AX
: 从栈顶取出数据送入AX
8086CPU的入栈和出栈操作都是以字为单位进行的。
注意:字型数据用两个单元存放,高地址单元放高8位,低地址单元放低8位。
疑问
CPU如何知道一段内存空间被当作栈使用?
执行PUSH和POP,如何知道哪个单元是栈顶单元?
分析
CPU如何知道当前要执行的指令所在的位置?
答: 寄存器CS和IP中存放着当前指令的段地址和偏移地址。
8086CPU中,有两个寄存器:
任意时刻,SS : SP 指向23当前栈顶元素。
PUSH AX
任意时刻,SS : SP 指向栈顶元素,当栈为空时,栈中没有元素,也就不存在栈顶元素。
所以SS : SP 只能指向栈的最底部单元下面的单元,该单元的偏移地址位栈最底部的字单元的偏移地址+2
栈最底部字单元的地址位1000:000E,所以栈空时,SP = 0010H.
POP AX
出栈后,SS : SP 指向新的栈顶1000EH,pop操作前的栈顶元素,1000CH处的2266H依然存在,但是已经不在栈中。
当再次执行push等入栈指令后,SS : SP移至1000CH,并在里面写入新的数据,它将被覆盖。
PUSH和POP指令可以在寄存器和内存之间传送数据
入栈出栈时,如果栈顶超界,可能会造成比较严重的后果。
因为栈空间之外的空间可能存放了具有其它用途的数据,代码等,这些数据,代码可能是我们自己的程序中的,也可能是别的程序中的。
8086CPU工作时,只考虑当前的情况:
编程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,pop等栈操作指令,修改的只是SP。也就是说是,栈顶的最大变化范围是:0~FFFFH
我们可以将长度位N(N<=64K)的一组地址连续,起始地址为16倍数的内存单元,当作栈使用,从而定义了一个栈段。