汇编:段地址 + 偏移地址

本文最后更新于:2022年11月3日 下午

段地址 + 偏移地址 = 物理地址
8086CPU通过段地址寄存器和基地址寄存器来计算得到真实的物理地址

段地址 + 偏移地址

8089CPU 的地址总线是20位的,但是寄存器是16位的,为了拼出这个物理地址,内部有个加法寄存器的部件会将段地址和偏移地址相加得到一个物理地址,然后输入输出电路将20位物理地址传送到地址总线。

段地址*16,其实就是后面补0,也就是说一个16进制数左移1位,16进制数一位最高表示15,所以16要进1,所以尾部补0

如果是10进制数 *10,也是会补零,一样的道理

段地址表示的一个内存的起始位置,偏移地址是具体的代码块

书上有个更形象的段地址和偏移地址的说法

如我告诉你我所在公司的地址

  1. 从家里走9856m到公司,这9856m可以认为是物理地址
  2. 从家里走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

csip8086CPU中最为关键的两个寄存器,它们组合起来表示了当前要读取指令的地址,CS为代码段寄寄存器,ip为指令指针寄存器。8086CPU有4个段寄存器 CS, DS, SS, ES, 8086CPU要访问内存时由这4个段寄存器提供内存单元地址

8086CPU中,任意时刻,CPUCS:IP指向的内容当作指令执行

  1. CS:IP指向的内存单元读取指令,读取的指令进入指令缓冲器
  2. IP=IP+所读指令的长度,从而指向下一条指令
  3. 执行指令,IP+1, 重复步骤1

8086CPU中,加点启动或者复位时,CSIP 被设置为 CS=FFFFFH, IP=00000H,所以8086CPU在刚启动的时候,CPU从内存FFFF0H单元中读取指令执行,FFFF0H8086PC机开机后执行的第一条指令

CPUCS:IP指向的内存单元看做指令。

修改CS,IP的指令

8086CPU中大多寄存器都可以通过mov ax, 2000H 这种方式来修改,也就是说通过mov指令来修改,但是8086CPU不允许通过mov指令修改CSIP寄存器,如果要修改CS,IP寄存器的内容,可以通过jmp 2AE3:3,执行后:CS=2AE3H, IP=00003H,CPU2AE33H处读取指令

字在内存中存储时,要用两个地址连续的内存单元存放,字的低位字节放在低地址单元中,高位地址放在高地址单元中

所以一个字型数据占据两个内存单元,如果要对他们进行相加之类的操作,要偏移两个地址,2,4

代码段

对于8086CPU机,我们可以根据需要,将一组内存单元定义为一个段,我们可以将一个长度小于N(N≤64KB)的一组代码,存在一组连续,起始地址为16的倍数的内存单元,这段内存用来存放代码的

假设这段10字节的指令,存放在123B0H~123B9H的一组内存单元中,我们就可以认为123B0H~123B9H这段内存是用来存放代码的,是一个代码段,他的代码段为123BH,长度为10字节。如果要将这段代码运行,需要将CSIP的地址指向这里,可设 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::00100001:0000 所指向的地址是一致的

如果第三行算错了,则后续的计算都是错误的


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议,转载请注明出处。