汇编:段地址 + 偏移地址
本文最后更新于:2022年11月3日 下午
段地址 + 偏移地址 = 物理地址
8086CPU
通过段地址寄存器和基地址寄存器来计算得到真实的物理地址
段地址 + 偏移地址
8089CPU
的地址总线是20
位的,但是寄存器是16
位的,为了拼出这个物理地址,内部有个加法寄存器的部件会将段地址和偏移地址相加得到一个物理地址,然后输入输出电路将20
位物理地址传送到地址总线。
段地址*16
,其实就是后面补0
,也就是说一个16
进制数左移1位,16
进制数一位最高表示15
,所以16
要进1
,所以尾部补0
如果是10进制数 *10
,也是会补零,一样的道理
段地址表示的一个内存的起始位置,偏移地址是具体的代码块
书上有个更形象的段地址和偏移地址的说法
如我告诉你我所在公司的地址
- 从家里走
9856m
到公司,这9856m
可以认为是物理地址 - 从家里走
9000m
到金融城,再走856m
到公司,9000m
可以认为是一个基础地址,第二个地址是相对于基础地址的偏移地址
第二种方式就是采用基础地址和偏移地址来得到的物理地址阿斯顿发斯蒂芬
再加一点难度,我们只能采用纸条来进行通信,那很显然,我需要一张可以容纳4
位数字的纸条,才能把9856
这个距离数据完整的表示完
但是由于资源有限,我没有能容纳4位数字的纸条,我手里只有能容纳3
位数字的纸条,于是我们预定好,我在第一张上写上900
(段地址), 第二张写上856
(偏移地址),假设我们事先约定好,那么当你接收到这两张纸条后,将 (900 * 10 + 856
) 就能够得出9856
这个数据了
而8086CPU
就是一台智能提供两张3
位数据纸条的CPU
一个物理地址有多种表示方式,通过段地址和偏移地址的不同组合,可以表示相同的一个物理地址(记住这点很重要)
2000H 1F60H 21F60H
2100H 0F60H
21F0H 0060H
21F6H 0000H
1F00H 2F60H
段寄存器
CS + IP
cs
和ip
和8086CPU
中最为关键的两个寄存器,它们组合起来表示了当前要读取指令的地址,CS
为代码段寄寄存器,ip
为指令指针寄存器。8086CPU
有4个段寄存器 CS, DS, SS, ES
, 8086CPU
要访问内存时由这4个段寄存器提供内存单元地址
在8086CPU
中,任意时刻,CPU
将CS:IP
指向的内容当作指令执行
- 从
CS:IP
指向的内存单元读取指令,读取的指令进入指令缓冲器 IP=IP+所读指令的长度
,从而指向下一条指令- 执行指令,
IP+1
, 重复步骤1
在8086CPU
中,加点启动或者复位时,CS
和 IP
被设置为 CS=FFFFFH
, IP=00000H
,所以8086CPU
在刚启动的时候,CPU
从内存FFFF0H
单元中读取指令执行,FFFF0H
是8086PC
机开机后执行的第一条指令
CPU
将CS:IP
指向的内存单元看做指令。
修改CS,IP的指令
8086CPU
中大多寄存器都可以通过mov ax, 2000H
这种方式来修改,也就是说通过mov
指令来修改,但是8086CPU
不允许通过mov
指令修改CS
和IP
寄存器,如果要修改CS,IP
寄存器的内容,可以通过jmp 2AE3:3
,执行后:CS=2AE3H, IP=00003H
,CPU
从2AE33H
处读取指令
字在内存中存储时,要用两个地址连续的内存单元存放,字的低位字节放在低地址单元中,高位地址放在高地址单元中
所以一个字型数据占据两个内存单元,如果要对他们进行相加之类的操作,要偏移两个地址,2,4
代码段
对于8086CPU
机,我们可以根据需要,将一组内存单元定义为一个段,我们可以将一个长度小于N(N≤64KB)
的一组代码,存在一组连续,起始地址为16
的倍数的内存单元,这段内存用来存放代码的
假设这段10字节的指令,存放在123B0H~123B9H
的一组内存单元中,我们就可以认为123B0H~123B9H
这段内存是用来存放代码的,是一个代码段,他的代码段为123BH
,长度为10
字节。如果要将这段代码运行,需要将CS
和IP
的地址指向这里,可设 CS=123BH, IP= 0000H
DS 和 [address]
ds
也是8086CPU
中的一个段寄存器,通常用来存放需要访问的数据段地址,如下例子,我们要读取10000H
单元的内容,可以用如下程序段进行
mov bx, 1000H
mov ds, bx
mov al, [0]
mov al, [0]
这段代码表示偏移量是0
,代码段的地址默认放在ds
中,指令执行时,8086CPU
会默认从ds
中取出
所以要用 mov al, [0]
完成数据1000:0
单元到al
的传送,这条指令执行时,ds
中的内容应未段地址1000H
,所以在这条指令之前应该讲1000H
送入到ds
。
如何将一个数据送入到一个段寄存器呢?以前我们用mov ax, 1
这样的指令来完成,从理论上讲,我们可以用同样的方式进行修改,如 mov ds 1000H
来将1000H
送入到ds
。但是8086CPU
实际上不支持将数据直接传入到段寄存器的操作。ds
是一个段寄存器,所以 mov ds, 1000H
这条指令是非法的,那如果要将1000H
传入到ds中
,可以用一个寄存器来进行中转,即先将1000H
传入到一个通用寄存器,如ax,bx
等,再将通用寄存器ax
的内容送入到ds
中
至于为什么8086CPU
不支持将数据直接传入到段寄存器中,这属于8086CPU
硬件设计的问题,我们只需要知道这一点就行了
如何将al
寄存器的数据送入到内存单元10000H
中
mov bx, 10000H
mov ds, bx
mov [0], al // 这条指令会将al的数据放到10000H中
测试1
指令 | ax | bx | cx | |
---|---|---|---|---|
mov ax, [0] | 1000:0存放的是 23,该数据是字型数据的低8位23H, 1000:1存放的是字型数据高8位,11H,所以 ax 的值为1123H | 1123H | ||
mov bx, [2] | 1000:2 字型数据的低8位22H,1000:3是字型数据的高8位,66H,所以bx的值为6622H | 6622H | ||
mov cx, [1] | 1000:1 字型数据的低8位11H,1000:2是字型数据的高8位,22H,所以cx的值为6622H | 2211H | ||
add bx, [1] | bx 6622H + 2211H 然后把结果8833H赋值为寄存器bx | 8833H | ||
add cx, [2] | cx 2211H + 6622H然后把结果8833H赋值为寄存器bx | 8833H |
测试2
这里注意,第一行是把 1000H
当做值来使用
第二行是把1000H
当做地址使用,因为ds
是段地址寄存器,第3
行[0]
是在取ds
寄存器中地址的第0
个偏移量,所以这也是当做地址来使用
指令 | ax | ds(段地址寄存器) | bx | |
---|---|---|---|---|
mov ax, 1000H | 这行是给ax寄存器直接赋值 | 1000H | ||
mov ds, ax | 给段地址寄存器ds设置为ax的值,也就是1000 | 1000H | ||
mov ax, 11316 | 11316 后面没有加H,表示是10进制,转到到16进制为 2C34,这行不会修改10000H-10003H内存中的值 | |||
mov [0], ax | [0] 表示 1000:0000, 它和ax的指向是一致的 也就意味着它把 10000H 修改为了34,100001H修改为了2C | |||
mov bx, [0] | 0偏移量的值上一行已经被修改了,所以bx的值会被修改为2C34H | 2C34H | ||
sub bx, [2] | bx的值在上一步已经算出了是 2C34H,1000:2是字型数据的低8位,22H,1000:3是字型数据的高8位,11H,所以bx的值为1122H, 再用bx - 1122,然后把结果1B12H赋值到寄存器bx | 1B12H | ||
mov [2], bx | 1000:0002的地址为12H, 1000:0003的地址为1BH |
测试
下题是一道很好的测试题,可以检验是否完全掌握汇编的地址运算规则,
此处需要注意第三行 0001:0000
对应的数据刚好是第二行的数据,只是表示形式不一样,0000::0010
和0001:0000
所指向的地址是一致的
如果第三行算错了,则后续的计算都是错误的
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议,转载请注明出处。