x86 - CPU架构/寄存器详解 (二) 实模式(8086模式)

本文详细讲解了8086 CPU架构,包括寄存器类型(通用寄存器、控制寄存器、段寄存器)、内存机制(分段机制与内存访问)、操作系统中的中断处理等。重点介绍了AX、BX、CX、DX等通用寄存器的特性和使用方法。
摘要由CSDN通过智能技术生成

系列文章

x86 - CPU架构/寄存器详解 (一)x86、8086、i386、IA-32 是什么?
x86 - CPU架构/寄存器详解 (二) 实模式(8086模式)
x86 - CPU架构/寄存器详解 (三) 保护模式
x86 - 分段与分页详解
x86 - 特权级别 CPL / RPL / DPL / IOPL
x86 - 操作系统:中断、陷阱、异常、故障、终止
x86 - 描述符详解:存储/系统段描述符、门描述符


  8086 具有 16 位的段寄存器、指令指针寄存器和通用寄存器(CS、SS、DS、ES、IP、AX、BX、CX、DX、SI、DI、BP、SP),因此,我们称它为 16 位的处理器。尽管它可以访问 1MB 的内存,但是只能分段进行,而且由于只能使用 16 位的段内偏移量,故段的长度最大只能是 64KB。8086 只有一种工作模式,即实模式;当然,这个名称是后来才提出来的。

一、处理器架构

在这里插入图片描述
  8086 CPU 中寄存器总共为 14 个,且均为 16 位 。即 AX,BX,CX,DX,SP,BP,SI,DI,IP,FLAG,CS,DS,SS,ES 共 14 个。而这 14 个寄存器按照一定方式又分为了通用寄存器,控制寄存器和段寄存器。
通用寄存器

缩写含义
AX,BX,CX,DX
称作为数据寄存器
AX (Accumulator):累加寄存器,也称之为累加器;
BX (Base):基地址寄存器;
CX (Count):计数器寄存器;
DX (Data):数据寄存器;
SP 和 BP
又称作为指针寄存器
SP (Stack Pointer):堆栈指针寄存器;
BP (Base Pointer):基指针寄存器;
SI 和 DI
又称作为变址寄存器
SI (Source Index):源变址寄存器;
DI (Destination Index):目的变址寄存器;

控制寄存器
  IP (Instruction Pointer):指令指针寄存器;
  FLAG:标志寄存器;
段寄存器
  CS (Code Segment):代码段寄存器;
  DS (Data Segment):数据段寄存器;
  SS (Stack Segment):堆栈段寄存器;
  ES (Extra Segment):附加段寄存器;

二、内存机制

分段机制

  想象一下,如果我们在程序中完全使用物理地址对数据、代码进行定位,那我们必须保证计算机会按照我们预想的那样,将程序完整的读取到我们指定的内存区域(实际上这是完全不可能的)。因此,为了能够让程序具有通用性,我们需要在编程时使用相对地址或者偏移地址,而不能使用真实的物理地址,因此引入了分段机制。当程序加载时,这些相对地址还要根据程序实际被加载的位置重新计算,从而保证数据能够从正确的位置被读取到。
  其实段也就是在编程时,我们将若干个地址连续的内存单元看做是一个段,然后通过将一个段地址左移 4 位形成基地址(取决于8086独特的编制方式),再通过这个基地址来定位这个段的起始地址,然后,再通过偏移地址便可以精确定位到段中的内存单元了。

  当采用分段策略之后,一个内存单元的地址实际上就可以用“段:偏移”或者 “段地址:偏移地址”来表示,这就是通常所说的逻辑地址。
  为了在硬件一级提供对“段地址:偏移地址”内存访问模式的支持,处理器至少要提供两个段寄存器,分别是代码段(Code Segment,CS)寄存器和数据段(Data Segment,DS)寄存器。对 CS 内容的改变将导致处理器从新的代码段开始执行。同样,在开始访问内存中的数据之前,也必须首先设置好 DS 寄存器,使之指向数据段。 (SS (Stack Segment) 栈段也很重要)
  除此之外,最重要的是,当处理器访问内存时,它把指令中指定的内存地址看成是段内的偏移地址,而不是物理地址。这样,一旦处理器遇到一条访问内存的指令,它将把 DS 中的数据段起始地址和指令中提供的段内偏移相加,来得到访问内存所需要的物理地址。

  注意,由于短地址是由16位寄存器左移4位得来的,因此 8086 处理器的逻辑分段,起始地址都是 16 的倍数,这称为是按 16 字节对齐的。而段偏移也是由16位寄存器得到的,因此段的最大长度为64K。

内存访问

  8086一共只有20位地址线(所以地址空间只有1MB),以及8个16位的通用寄存器,以及4个16位的段寄存器。所以为了能够通过这些16位的寄存器去构成20位的主存地址,必须采取一种特殊的方式。当某个指令想要访问某个内存地址时,它通常需要用下面的这种格式来表示:

	(段基址:段偏移量)

  第一个字段是段基址,它的值是由段寄存器提供的。段寄存器有4种,%cs,%ds,%ss,%es。具体这个指令采用哪个段寄存器是由这个指令的类型来决定的。比如要取指令就是采用 %cs 寄存器(jmp, call),要读取或写入数据就是 %ds 寄存器(mov),如果要对堆栈操作就是 %ss 寄存器(push, pop)。总之,不管什么指令,都会有一个段寄存器提供一个16位的段基址。
  第二字段是段内偏移量,代表你要访问的这个内存地址距离这个段基址的偏移。它的值就是由通用寄存器来提供的,所以也是16位。

  那么问题来了,两个16位的值如何组合成一个20位的地址呢?8086 采用的方式是把段寄存器所提供的段基址先向左移4位。这样就变成了一个20位的值,然后再与段偏移量相加。所以算法如下:

	物理地址 = 段基址<<4 + 段内偏移

  所以假设 %cs中的值是 0xff00,%ax = 0x0110。则 (%cs:%ax) 这个地址对应的真实物理地址是

	0xff00<<4 + 0x0110 = 0xff110。

  上面就是实模式访问内存地址的原理。

通用寄存器

  • 8086 处理器内部有 8 个 16 位的通用寄存器,分别被命名为 AX、BX、CX、DX、SI、DI、BP、SP。“通用”的意思是,它们之中的大部分都可以根据需要用于多种目的。
  • 因为这 8 个寄存器都是 16 位的,所以通常用于进行 16位的操作。比如, 可以在这 8 个寄存器之间互相传送数据,它们之间也可以进行算术逻辑运算;也可以在它们和内存单元之间进行 16位的数据传送或者算术逻辑运算。
  • 同时,这 8 个寄存器中的前 4 个,即 AX、BX、CX 和 DX,又各自可以拆分成两个 8位的寄存器来使用,总共可以提供 8 个 8 位的寄存器 AH、AL、BH、BL、CH、CL、DH 和DL。这样一来,当需要在寄存器和寄存器之间,或者寄存器和内存单元之间进行 8 位的数据传送或者算术逻辑运算时,使用它们就很方便。

在这里插入图片描述
  将一个 16 位的寄存器当成两个 8 位的寄存器来用时,对其中一个 8 位寄存器的操作不会影响到另一个 8 位寄存器。举个例子来说,当你操作寄存器 AL 时,不会影响到 AH 中的内容。

AX 寄存器

  AX 的另外一个名字叫做累加寄存器或者简称为累加器,可以说是使用率最高的寄存器;除此之外,一些特殊指令都需要使用AX进行存储和运算:

  • 8086 处理器提供了除法指令 div,它可以做两种类型的除法。
    (1) 第一种类型是用 16 位的二进制数除以 8 位的二进制数。在这种情况下,被除数必须在寄存器 AX 中,必须事先传送到 AX 寄存器里。除数可以由 8 位的通用寄存器或者内存单元提供。指令执行后,商在寄存器 AL 中,余数在寄存器 AH 中;
    (2) 第二种类型是用32 位的二进制数除以16 位的二进制数。在这种情况下,因为 16 位的处理器无法直接提供 32 位的被除数,故要求被除数的高 16 位在DX 中,低 16 位在 AX 中,用“DX:AX”来表示 32 位的被除数
  • MUL 指令可以用 8 位的通用寄存器或者内存单元中的数和寄存器 AL 中的内容相乘,结果是 16 位,在 AX 寄存器中;也可以用 16 位的通用寄存器或者内存单元中的数和寄存器 AX 中的内容相乘,结果是 32 位,高 16 位和低 16 位分别在 DX 和 AX 中。
BX 寄存器

  除了暂存一般性数据的功能外,BX 作为通用寄存器的一种,BX 主要还是用于其专属功能 – 寻址(寻址物理内存地址)上,BX 寄存器中存放的数据一般是用来作为偏移地址使用的。
  在 8086 处理器上,如果要用寄存器来提供偏移地址(以 […] 的方式使用),只能使用 BX、SI、DI、BP,不能使用其他寄存器。 比如:

	mov [bx],dl  

  指令的作用是把 DL 中的内容,传送到以 DS 的内容为段地址,以 BX 的内容为偏移地址的内存单元中去。注意,指令中的中括号是必需的,否则就是传送到 BX 中,而不是 BX 的内容所指示的内存单元了。

CX 寄存器

  CX 中的 C 被翻译为 Counter 也就是计数器的功能,当在汇编指令中使用循环 LOOP 指令时,可以通过 CX 来指定需要循环的次数;
  loop 指令的功能是重复执行一段相同的代码,处理器在执行它的时候会顺序做两件事:

  • 将寄存器 CX 的内容减一;
  • 如果 CX 的内容不为零,转移到指定的位置处执行,否则顺序执行后面的指令。
DX 寄存器

  DX 寄存器和 AX一样,都需要用在特殊指令 DIV,MUL中:
  即当在使用 DIV 指令进行除法运算时,如果除数为 16 位时,被除数将会是 32 位,而被除数的高 16 位就是存放在 DX 中,而且执行完 DIV 指令后,本次除法运算所产生的余数将会保存在 DX 中;
  同时,在执行 MUL 指令时,如果两个相乘的数都是 16 位的话,那么相乘后产生的结果显然需要 32 位来保存,而这 32 位的结果的高 16 位就是存放在 DX 寄存器中 。

指针寄存器 BP

  这里只介绍BP,SP会和SS一起介绍(堆栈);
  BP (Base Pointer)也就是基指针寄存器,它和其他的几个用来进行寻址操作所使用的寄存器(还有 BX,SI,DI)没有太大的区别。
  当以 […] 的方式访问内存单元而且在 […] 中使用了寄存器 BP 的话,如果在指令中没有明确或者说是显示的给出段地址时,段地址则使用默认的 SS 寄存器中的值(BX,SI,DI 会默认使用 DS 段寄存器)
  比如 DS:[BP] 则在这里明确给出了段地址位于 DS 中,这里代表的内存单元即是段地址为 DS ,偏移量为 BP 寄存器中的值的内存单元,而如果单单是使用 [BP] 的话,则代表的内存单元是段地址为 SS,偏移量为 BP 寄存器中的值的内存单元;
  并且 BP 寄存器主要适用于给出堆栈中数据区的偏移,从而可以方便的实现直接存取堆栈中的数据。

变址寄存器 SI/DI

  变址寄存器和上面介绍的指针寄存器(也就是 BP 和 SP),它们的功能其实都是用于存放某个存储单元地址的偏移,或者是用于某组存储单元开始地址的偏移,即作为存储器指针使用。
  SI (Source Index) 是源变址寄存器,DI (Destination Index) 即是目的变址寄存器,8086 CPU 中的 SI 寄存器和 DI 寄存器其实和 BX 寄存器的功能是差不多的,只是有一些特殊指令必须要用到它们。
  8086 提供了 movsb 和 movsw 指令:

  • 这两个指令通常用于把数据从内存中的一个地方批量地传送(复制)到另一个地方,处理器把它们看成是数据串。movsb 的传送是以字节为单位的,而 movsw 的传送是以字为单位的。
  • movsb 和 movsw 指令执行时,原始数据串的段地址由 DS 指定,偏移地址由 SI 指定,简写为 DS:SI;要传送到的目的地址由 ES:DI 指定;传送的字节数(movsb)或者字数(movsw)由 CX 指定。
  • 标志寄存器 FLAGS 第 10 位是方向标志 DF(Direction Flag),通过将这一位清零或者置 1,就能控制 movsb 和 movsw 的传送方向(正向传送是指传送操作的方向是从内存区域的低地址端到高地址端;反向传送则正好相反)
  • 不管是正向传送还是反向传送,也不管每次传送的是字节还是字,每传送一次,CX 的内容自动减一。
  • 注意:单纯的 movsw 只能执行一次,如果希望处理器自动地反复执行,需要加上指令前缀 rep(repeat),意思是 CX 不为零则重复。rep movsw 的操作码是0xF3 0xA5,它将重复执行 movsw 直到 CX 的内容为零。

其他寄存器

CS 寄存器 和 IP 寄存器

  CS:IP 两个寄存器指示了 CPU 当前将要读取的指令的地址,其中 CS 为代码段寄存器,而 IP 为指令指针寄存器 。
  IP 是指令指针(Instruction Pointer)寄存器,它只和 CS 一起使用,而且只有处理器才能直接改变它的内容。当一段代码开始执行时,CS 指向代码段的起始地址,IP 则指向段内偏移。
  这样,由 CS 和 IP 共同形成逻辑地址,并由总线接口部件变换成物理地址来取得指令。然后,处理器会自动根据当前指令的长度来改变 IP 的值,使它指向下一条指令。

SS 寄存器和 SP 寄存器

  和代码段、数据段和附加段一样,堆栈也被定义成一个内存段,叫堆栈段(Stack Segment),由段寄存器 SS 指向。SS 指向栈顶,SP 存储栈顶的偏移地址,在任何时刻,SS:SP 都是指向栈顶元素
  针对堆栈的操作有两种,分别是将数据推进堆栈(push)和从堆栈中弹出数据(pop)。简单地说,就是压栈和出栈。压栈和出栈只能在一端进行,所以需要用堆栈指针寄存器 SP(Stack Pointer)来指示下一个数据应当压入堆栈内的什么位置,或者数据从哪里出栈。栈的内存结构是从上到下扩展的

  • 当使用 PUSH 指令向栈中压入 1 个字节单元时,SP = SP - 1;即栈顶元素会发生变化;
  • 当使用 POP 指令从栈中弹出 1个字节单元时, SP = SP + 1;即栈顶元素会发生变化;
DS 寄存器和 ES 寄存器

  顾名思义,DS是数据段(Data Segment)寄存器,ES是附加段(Extra Segment)寄存器。
  通常在使用一些数据操作指令时,默认将DS作为段基址,比如:

	MOV [BX],AL

  当我们定义好段地址后,每一次 CPU 执行到 [BX] 时,便会默认的从 DS 中取值,并且将取得的值作为段地址,因此,当 [BX] 为 0001H 时,CPU 会从 DS 中取得一个 1000H ,由这两个一合成即可以得到正确的物理地址 1000H:0001H 。
  注意:8086 CPU 不支持直接将一个数据送入段寄存器中,一般是先将数据送入通用寄存器,再由通用寄存器送入段寄存器。

标志寄存器(FLAG)

  标志寄存器设计为16位,实际使用9位:
   其中6位用以存放算术逻辑单元运算后的结果特征,称为状态标志;
   另外3位通过人为设置,用以控制8086的三种特定操作,称为控制标志。
在这里插入图片描述
  6个状态标志位定义如下:

位置英文中文作用
D0CF(Carry FLag)进位标志用于反映运算是否产生进位或借位。如果运算结果的最高位产生一个进位或借位,则CF置1,否则置0。运算结果的最高位包括字操作的第15位和字节操作的第7位。移位指令也会将操作数的最高位或最低位移入CF。
D2PF(Parity FLag)奇偶标志用于反映运算结果低8位中“1”的个数。“1”的个数为偶数,则PF置1,否则置0。
D4AF(Auxiliary Carry FLag)辅助进位标志算数操作结果的第三位(从0开始计数)如果产生了进位或者借位则将其置为1,否则置为0,常在BCD(binary-codedecimal)算术运算中被使用。
D6ZF(Zero FLag)零标志用于判断结果是否为0。运算结果0,ZF置1,否则置0。
D7SF(Sign FLag)符号标志用于反映运算结果的符号,运算结果为负,SF置1,否则置0。因为有符号数采用补码的形式表示,所以SF与运算结果的最高位相同。
D11OF(OverFlow FLag)溢出标志反映有符号数加减运算是否溢出。如果运算结果超过了8位或者16位有符号数的表示范围,则OF置1,否则置0。

  3个控制标志位定义如下:

位置英文中文作用
D8TF(Trap FLag)跟踪/陷阱/单步标志当TF被设置为1时,CPU进入单步模式,所谓单步模式就是CPU在每执行一步指令后都产生一个单步中断。主要用于程序的调试。8086/8088中没有专门用来置位和清零TF的命令,需要用其他办法。
D9IF(Interrupt-Enable FLag)中断标志决定CPU是否响应外部可屏蔽中断请求。IF为1时,CPU允许响应外部的可屏蔽中断请求。
D11DF(Direction FLag)方向标志决定串操作指令执行时有关指针寄存器调整方向。当DF为1时,串操作指令按递减方式改变有关存储器指针值,每次操作后使SI、DI递减。

三、参考资料

  《x86汇编语言 从实模式到保护模式》
  https://blog.csdn.net/weixin_40913261/article/details/90762210
  https://blog.csdn.net/weixin_42109012/article/details/100148721
  https://blog.csdn.net/sky1679/article/details/89785382
  https://blog.csdn.net/song_lee/article/details/105297902
  https://www.cnblogs.com/wanghetao/archive/2011/10/28/2228130.html
  https://zhuanlan.zhihu.com/p/272135463
  https://www.cnblogs.com/nullecho/p/10266467.html
  https://www.cnblogs.com/kukudi/p/11416993.html

  • 12
    点赞
  • 49
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值