从寄存器到内存访问(程序重定位角度)逐步认识8086处理器

一、8086的通用寄存器

8086处理器有8个16位的通用寄存器
在这里插入图片描述
寄存器中的最低位是什么?最高位是什么?如何区分?
答:高位在左,低位在右。如图所示:
在这里插入图片描述
这个16位的寄存器存储的数据的最高位是0,最低位是1。不过为了方便,寄存器内容一般用16进制给出。如下图:
在这里插入图片描述
前四个通用寄存器AXBXCXDX可以拆分位8个8位的寄存器来使用,来提供8位数据的计算。AX拆分为AHAL。BX拆分为BHBL。CX拆分为CHCL。DX拆分为DHDL。如下图左侧部分:
在这里插入图片描述
详细的看一下16位寄存器如何拆分成2个8位寄存器(以AX为例):
在这里插入图片描述
由上图可见,高8位为AH寄存器,低8位为AL寄存器。

AX的长度是两个字节,一个字。(一个字就是两个字节)
AH可AX是高字节部分,AL是AX的低字节部分。
在这里插入图片描述
因为AH和AL是由AX拆分而来,所以AH和AL中的内容发生变化,AX的内容会随之变化。换而言之,它们是相同的物理硬件,只不过使用不同符号表示不同范围,小范围内数据变化大范围数据必然变化,因为数据是存储在同一个地方的。所以,AH和AL的值的变化是影响着AX的。

二、8086的内存访问和字节序

Intel8086有16根数据线,和寄存器宽度一致。寄存器C05B通过数据线到达内存之后,被拆分成两个8位,其中C0是高字节部分,5B是低字节部分,然后,5B被写入0002的地址单元,C0被写入0003的地址单元。地址位0002的地址单元为低地址单元,地址为0003的地址单元为高地址单元。
在这里插入图片描述
当写入一个字时,如果低字节写入内存的低地址单元,高字节写入内存的高地址单元,成之为低端字节序

读的时候,也是一样,从0002地址单元读取一个字,传送到SI寄存器:因为读取一个字,低地址单元的5B和高地址单元的C0取出,在数据线上合并成一个字C05B,最后传送到寄存器SI。

有些寄存器是8位的,如AH和AL。如果AH内容是8D,将其写入内存单元0002:寄存器和内存单元的长度是匹配的都是一字节,问题在于数据线的宽度是16位的。在传送8位的数据时,数据线只使用了一半,另外一半是不用的。 因此,8D通过数据线的一半进入内存,然后写入内存地址0002的地址单元。如下图:
在这里插入图片描述
读出时候也是这样,从内存中读取到AL,从0002内存单元读取,通过数据线的一半写入AL。如下图:

在这里插入图片描述

三、程序的分段

3.1 常见名词含义

如果指令存储在一起,可称为代码段

处理器是可以自动取指令和执行指令的器件。为了利用计算机解决问题我们需要编排指令,这个过程叫做编程。编程的结果是生成了一个程序。看下图:

在这里插入图片描述

我们在内存中编排了一个程序,程序是从内存地址为0的地方开始存放的,处理器时按顺序取指令和执行指令,所以指令必须是一条一条集中存放,图中,黄色部分是集中存放的指令,指令很多,只是列出了一开始的一部分。

自然的,这些指令集中在一起行程了程序的代码部分,或者说指令部分。由于这些指令占用了连续的区段,所以称为程序的代码段。这里的代码段起始地址为0。指令执行时需要用到数据,数据也是放在内存中,在图中的蓝色部分,数据很多,这里列出一部分。

自然的,数据集中在一起形成了程序的数据部分,由于这些数据在内存中占用了连续的区段,所以我们叫程序的数据段,图中的数据段起始地址为0c00。

指令是由操作码和操作数共同组成,对于处理器来说,操作码隐藏了如何处理处理该指令的信息,比如指令是做什么的以及怎么去做。

3.2 指令执行过程

在这里插入图片描述

上图中,第一条指令是从地址为0的地方开始存放的,共三个字节。其中,第一个字节的A1是操作码(隐含三条信息:第一,这是一条传送指令。第二,被传送的位置是AX寄存器。第三,被传送的数据在内存中的另一个地方,地址是16位的,紧跟在操作码的后面,因为8086处理器是低端字节序,所以这条地址是0C00)。总之,这条指令的功能是将内存地址0C00处的一个字传送到AX寄存器。由于被传送的目标是AX寄存器,是16位,所以这条指令执行时再次访问内存,从内存地址0C00处取得一个字,3C和05合成一个053C,然后再传给AX寄存器,传完之后,寄存器AX的内容是053C。

再来看第二条指令。
在这里插入图片描述

上图中,第二条指令是从内存地址0003开始存放的,一共是四个字节。其中,前两个字节03和06是操作码,显然,这个操作码很长,是由两个字节组成的。操作码03和06所隐藏的信息是(第一,这条指令是一条加法指令。第二,第一个相加的数字位于AX寄存器里,而且相加的结果保存再AX寄存器。 第三,第二个相加的数字位于内存中的另外一个地方,它的地址紧跟再操作码的后面,是由两个字节组成的,在这里,操作码后面的两个字节是02和0C,由于8086低端字节序,所以这个地址是0C02。)。总之,这条指令作用是将寄存器AX的内容和内存地址0C02处的字相加,结果在AX中。

四、程序的重定位难题

为了自动的取指令和执行指令,处理器需要一个寄存器来跟踪指令,假定它的名字叫做IPR,IPR的内容始终是下一条将要执行的指令,在程序开始执行之前,我们需要将第一条地址传送到这个寄存器,我们程序从0开始执行,第一条指令地址就是程序开始的地方,也就是0。因此我们把0传送到 IPR,IPR的地址此时为0。当程序开始执行时,处理器把IPR中的内容,放到地址线上,第一次发出的地址是0,所以是从内存地址为0的地方取出指令并加以执行, 第一次取出的是这条指令:

在这里插入图片描述
第一条指令的内容是将内存地址0C00出的 一个字传送到AX寄存器。这条指令在执行时,从0C00取出一个字,然后传送到AX寄存器:

在这里插入图片描述
与此同时,寄存器IPR的内容被更新为下一条指令的地址0003,这个地址怎么来的?很简单,用当前指令的地址加上当前指令的长度就可以得到下一条指令的地址。当前指令地址时0,长度为3,所以下一条指令地址就是3。当第一条指令执行之后,处理器再用IPR的内容去内存里取出第二条指令。这是第二条指令:

在这里插入图片描述

这条指令的内容是将寄存器AX的内容和内存地址0C02处的字相加,结果在AX中。指令执行时,从0C02处取出内容0F8B然后和AX中内容相加后重新写回AX。与此同时,寄存器IPR的内容自动更新为下一条指令的地址0007(0003+4)。

目前来看,程序工作很好。但如果程序的位置换了,还好使吗?下面的是换位置(内存地址)后的程序
在这里插入图片描述

程序中的指令和数据都没有变化,只不过每一条指令和每一个数据在内存中的地址在内存中发生了改变。如:第一条地址的指令是0000,现在是1000。为了从新的地方取指令和执行指令,IPR的内容需要更新,开始执行前设置成1000。这是程序的新地址。

程序执行时,处理器用IPR的内容发出地址1000去取第一条指令,第一条指令的内容是将内存地址0C00出的一个字传送到AX寄存器,但是由于程序刚刚改变了位置,那个数字的地址已经不是0C00了而是1C00,但是这个程序执行时依然从老地方0C00取,这就出错了。发生这样的事情是因为我们在内存中使用绝对地址(物理地址),如第一条指令的0C00和第二条指令的0C02,这样的绝对地址在内存中是无法在内存中自由浮动,用术语来说是无法重定位的。

要解决这个问题可以在程序加载时修改每一个指令,去临时改变,但是很荒谬。一个程序的指令少则几十条,多则几千几万条,几千万条,这么多怎么修改呢?所以能够进行重定位是对程序的基本要求。在计算机中运行着各种各样的程序,每个程序在启动时加载的位置都是随机的,哪里有空闲的地方就会加载到哪里。因此,一个良好的程序必须能够在内存中良好自由浮动而不会影响它的正确执行,但是这就要求我们不能在指令中使用物理地址,那么,到底我们应该怎么做呢?

引入段地址和偏移地址!

五、段地址和偏移地址

我们通过引入数据段寄存器来解决程序的重定位问题

指令中不能使用物理地址,否则无法解决重定位问题,如何解决呢?

先观察数据段,053C和数据段起始地址的距离是0,所以偏移量是0.。再看0F8B和数据段起始地址的距离是2个字节,所以这个字距离起始地址的偏移量是2.。因为这个原因,每个字都有两个属性,一个是物理地址(1C00、1C02),另一个是相对数据段起始的偏移量(分别0和2),从现在开始,把这两个字相对起始地址的偏移量叫做偏移地址。图中的“+”表明是偏移地址。

在这里插入图片描述

代码段中做对应修改,物理地址改为偏移地址。

在这里插入图片描述

为了配合这种改变,我们在处理器内添加了数据段寄存器DSR。DSR用来保存数据段的起始地址。

在这里插入图片描述

那么,在程序开始执行前,将第一条指令的物理地址传送到 IPR(指令指针寄存器),现在IPR的内容是1000,然后将数据段的其实地址1C00传送到 DSR(数据段寄存器),现在处理器开始取指令并执行指令:先用IPR发出1000去访问内存,取得第一条指令,这条指令是将偏移地址为0000处的一个字传送到AX寄存器,在执行指令时需要用到寄存器DSR,此时用DSR的内容1C00和指令中的偏移地址相加,形成物理地址1C00,于是处理器将1C00作为地址发送给内存,然后从内存1C00处取得一个字053C,然后传送给AX寄存器。这是第一条指令的执行过程。

与此同时,寄存器IPR的内容更新为下一条指令的地址1003,那么在第一条指令执行之后,处理器再次利用IPR的内容,向内存发出地址1003去取指令,取得第二条指令,这条指令的内容为将寄存器AX的内容和偏移地址为0002处的字相加,结果在AX中。在指令执行时,需要用到段寄存器DSR,此时用DSR的内容1C00和指令中的偏移地址002相加,形成物理地址1C02,然后用1C02访问内存,然后从内存1C02处取得一个字0F8B,然后将0F8B和AX原有内容相加再送给AX寄存器。这是第二条指令的执行过程。

显然,经过这样的软硬件改变之后,程序不再用做任何修改就可以随便放在内存中的任何地方加以执行。

六、8086内存访问困境

我们来了解INTEL8086处理器因硬件设计的特点而存在访问内存时所面临的挑战

8086有16根数据线,一次可访问一字数据,也就是两个字节的数据。8086有20根地址线,可以访问多少内存呢?00000-FFFFF。

在这里插入图片描述
在这里插入图片描述
20根地址线最多可以访问1048576个单元,1048576个字节,就是1MB。

要取指令和数据就必须发出地址,Intel8086处理器内部集成了和内存访问有关的寄存器,其中包括代码段寄存器CS,和数据段寄存器DS。

原则上,代码段寄存器CS用于存储代码段的起始物理地址,通过代码段CS可以跟踪每一条指令的地址,处理器可以用它实现自动取指令和执行指令。

原则上,数据段寄存器DS用于保存数据段的起始物理地址。在指令执行期间,数据段DS的内容和指令中的偏移地址相加就得到了物理地址,通过物理地址就可以取得操作数。

但是非常遗憾,这些寄存器都是16位的,容纳不了20位的内存地址,16位宽度的寄存器容纳不了20位地址线的地址。

怎么办呢?下节分解!

七、8086选择段地址的策略

代码段寄存器CS和数据段寄存器DS,用来保存代码段和数据段的起始地址,原则上,任何内存地址都可以用作段地址,但是很遗憾,CS和DS都是16位的,容纳不了20位的物理地址,不过我们发现有些内存地址是以0为结尾的,如 00000、00010 等等,如下图:

在这里插入图片描述

试想,如果将这些地址的末尾的0去掉,剩下的部分就可以放在寄存器里了,比如FFFF0,去掉0为FFFF,就可以放入寄存器CS中。

在这里插入图片描述
显然,8086系统中,由于段寄存器长度限制,不是所有的内存地址都可以成为段地址,只有哪些以0为结尾的段地址才能成为段地址。

如代码段的起始地址30CE0,将末尾0去掉,剩下30CE就可以传到CS。程序的数据段起始于物理地址33CE0,去掉0,将33CE存到DS。反过来,用段寄存器访问内存时把末尾的0再加上,就可以访问原来20位的地址。在很多书上,这个过程是把地址左移4位或乘16。

按照我们原来的设想,段寄存器CS用来跟踪每一条指令的物理地址,比如说第一条指令的物理地址是30CE0,指令的长度是3个字节,在第一条指令执行期间,处理器必须将CS的内容修改为第二条指令的地址30CE3,但是CS的长度是16位,装不下下一条指令的地址,这又是一个难题。

预知如何获取指令地址?下节分解。

八、8086的内存访问过程

上节说过,段寄存器是无法取指令的,因为它的宽度不够只有16位。请考虑一下,代码段内虽然每一条指令都有它的物理地址,但它也有一个相对段起始处的偏移地址,比如这个代码段,第一条指令的偏移地址是0000,因为它的物理地址就是代码段的起始地址,第二条指令的偏移地址是0003,因为它相对代码段的偏移量是3个字节。

在这里插入图片描述

因为这个原因,8086内部集成了一个指令指针寄存器 IP专门用来保存指令的偏移地址。下面我们通过跟踪这个程序的执行过程来了解这些寄存器是如何工作的以及8086是如何取指令和执行指令的。

在程序开始执行前,我们需要先把代码段的起始地址30CE0右移4位,把末尾的0去掉得到30CE,并传送到代码段寄存器CS,此时,寄存器CS的内容是30CE。如下图:

在这里插入图片描述

接着把数据段的物理起始地址右移4位,或者说把末尾的0去掉得到33CE,然后传送到数据段寄存器DS,此时DS的内容是33CE。最后我们把第一条指令的偏移地址传送到指令指针寄存器 IP,此时的IP的内容是0000。如下图:

在这里插入图片描述

此时,段寄存器都有了初始值。

那么现在处理器开始取指令,将段寄存器CS的内容左移4位,得到20位的30CE0,然后用30CE0和指令指针寄存器IP里的0000相加,得到20位的物理地址30CE0,然后把这个地址送到地址线上去访问内存,这将在物理地址30CE0处取得第一条指令,并加以执行。这条指令是将偏移地址0000处的一个字传送到AX寄存器。这将再次访问内存,指令执行时访问内存需要用到数据段寄存器DS,此时,是把DS中内容33CE左移4位,得到20位 的33CE0,然后用33CE0和指令中的偏移地址相加,这将得到一个20位的物理地址33CE0。如下图:

在这里插入图片描述

然后把这个地址送入地址线去访问内存, 这将从物理地址33CE0处得到一个字053C,然后送入AX寄存器,与此同时,指令指针寄存器IP的内容将自动修改为下一条指令的偏移地址0003,这个数字使用IP里原有的数字0000,加上第一条指令的长度3得到的。 现在寄存器IP的内容是0003。

执行完第一条指令之后,处理器再次进入取指令阶段,将段寄存器CS的内容左移4位,得到20位的数字30CE0,将其和指令指针寄存器IP的内容0003相加,得到20位的地址30CE3放到地址线访问内存,从30CE3取得第二条指令,加以执行。指令的内容是将AX的内容和偏移地址0002处字相加存在AX中,这将再次访问内存,指令执行时,访问内存时需要用到数据段寄存器DS,此时将DS内容左移4位得到33CE0,和偏移地址0002相加得到33CE2送到地址线访问内存,从33CE2取出0F8B和AX中内容相加结果存在AX中。如下图:

在这里插入图片描述
与此同时,指令指针寄存器IP的内容自动修改为下一条指令执行的偏移地址0007,这个是由IP中原有的3和原有的第二个指令的长度相加得到的。

九、逻辑地址和分段的灵活性

段寄存器:用于存储地址

通过前面所讲述,已经知道了8086内存分段的原理,比如下图中给出了一些内存单元的地址:

在这里插入图片描述
那么这些地址中,哪些可以作为8086的地址呢?以0为结尾的可以!

在这里插入图片描述

把圈圈的看成一个段,那么在段内,这些内存单元的偏移地址分别为0000 0001 0002 0003 0004 0005。为方便描述内存单元地址和段地址的关系,推荐一种新方法标记内存单元地址。表示如下图:

在这里插入图片描述
比如这里段内第一个内存单元的地址是 65C7 : 0000,其中65C7是末尾去掉了0的段地址(是一个16位的段地址),冒号后面的0000是段内的偏移地址。可以清楚的知道段的起始地址和偏移地址,这种方式表示叫做逻辑地址。

8086系统中,访问任何一个内存单元都是用段寄存器乘16形成20位的段地址,在和偏移地址相加。

8086分段很灵活,一个逻辑地址可以对应多个物理地址。

在这里插入图片描述
8086中偏移地址只能是16位的,最大是FFFF。10007非法。

前面叙述中,段内偏移地址最小是0最大是FFFF。所以段的长度最大是64KB。如下图:
在这里插入图片描述

该段最后一个内存单元的物理地址是多少?怎么求?
答:用最后一个单元的逻辑地址的A037(逻辑段地址)乘16和逻辑偏移地址相加得出,如下图:

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_44417441/article/details/135296338