x86 - CPU架构/寄存器详解 (一)x86、8086、i386、IA-32 是什么?
x86 - CPU架构/寄存器详解 (二) 实模式(8086模式)
x86 - CPU架构/寄存器详解 (三) 保护模式
x86 - 分段与分页详解
x86 - 特权级别 CPL / RPL / DPL / IOPL
x86 - 操作系统:中断、陷阱、异常、故障、终止
x86 - 描述符详解:存储/系统段描述符、门描述符
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。
上面就是实模式访问内存地址的原理。
将一个 16 位的寄存器当成两个 8 位的寄存器来用时,对其中一个 8 位寄存器的操作不会影响到另一个 8 位寄存器。举个例子来说,当你操作寄存器 AL 时,不会影响到 AH 中的内容。
AX 的另外一个名字叫做累加寄存器或者简称为累加器,可以说是使用率最高的寄存器;除此之外,一些特殊指令都需要使用AX进行存储和运算:
除了暂存一般性数据的功能外,BX 作为通用寄存器的一种,BX 主要还是用于其专属功能 – 寻址(寻址物理内存地址)上,BX 寄存器中存放的数据一般是用来作为偏移地址使用的。
在 8086 处理器上,如果要用寄存器来提供偏移地址(以 […] 的方式使用),只能使用 BX、SI、DI、BP,不能使用其他寄存器。 比如:
mov [bx],dl
指令的作用是把 DL 中的内容,传送到以 DS 的内容为段地址,以 BX 的内容为偏移地址的内存单元中去。注意,指令中的中括号是必需的,否则就是传送到 BX 中,而不是 BX 的内容所指示的内存单元了。
CX 中的 C 被翻译为 Counter 也就是计数器的功能,当在汇编指令中使用循环 LOOP 指令时,可以通过 CX 来指定需要循环的次数;
loop 指令的功能是重复执行一段相同的代码,处理器在执行它的时候会顺序做两件事:
DX 寄存器和 AX一样,都需要用在特殊指令 DIV,MUL中:
即当在使用 DIV 指令进行除法运算时,如果除数为 16 位时,被除数将会是 32 位,而被除数的高 16 位就是存放在 DX 中,而且执行完 DIV 指令后,本次除法运算所产生的余数将会保存在 DX 中;
同时,在执行 MUL 指令时,如果两个相乘的数都是 16 位的话,那么相乘后产生的结果显然需要 32 位来保存,而这 32 位的结果的高 16 位就是存放在 DX 寄存器中 。
这里只介绍BP,SP会和SS一起介绍(堆栈);
BP (Base Pointer)也就是基指针寄存器,它和其他的几个用来进行寻址操作所使用的寄存器(还有 BX,SI,DI)没有太大的区别。
当以 […] 的方式访问内存单元而且在 […] 中使用了寄存器 BP 的话,如果在指令中没有明确或者说是显示的给出段地址时,段地址则使用默认的 SS 寄存器中的值(BX,SI,DI 会默认使用 DS 段寄存器),
比如 DS:[BP] 则在这里明确给出了段地址位于 DS 中,这里代表的内存单元即是段地址为 DS ,偏移量为 BP 寄存器中的值的内存单元,而如果单单是使用 [BP] 的话,则代表的内存单元是段地址为 SS,偏移量为 BP 寄存器中的值的内存单元;
并且 BP 寄存器主要适用于给出堆栈中数据区的偏移,从而可以方便的实现直接存取堆栈中的数据。
变址寄存器和上面介绍的指针寄存器(也就是 BP 和 SP),它们的功能其实都是用于存放某个存储单元地址的偏移,或者是用于某组存储单元开始地址的偏移,即作为存储器指针使用。
SI (Source Index) 是源变址寄存器,DI (Destination Index) 即是目的变址寄存器,8086 CPU 中的 SI 寄存器和 DI 寄存器其实和 BX 寄存器的功能是差不多的,只是有一些特殊指令必须要用到它们。
8086 提供了 movsb 和 movsw 指令:
CS:IP 两个寄存器指示了 CPU 当前将要读取的指令的地址,其中 CS 为代码段寄存器,而 IP 为指令指针寄存器 。
IP 是指令指针(Instruction Pointer)寄存器,它只和 CS 一起使用,而且只有处理器才能直接改变它的内容。当一段代码开始执行时,CS 指向代码段的起始地址,IP 则指向段内偏移。
这样,由 CS 和 IP 共同形成逻辑地址,并由总线接口部件变换成物理地址来取得指令。然后,处理器会自动根据当前指令的长度来改变 IP 的值,使它指向下一条指令。
和代码段、数据段和附加段一样,堆栈也被定义成一个内存段,叫堆栈段(Stack Segment),由段寄存器 SS 指向。SS 指向栈顶,SP 存储栈顶的偏移地址,在任何时刻,SS:SP 都是指向栈顶元素 。
针对堆栈的操作有两种,分别是将数据推进堆栈(push)和从堆栈中弹出数据(pop)。简单地说,就是压栈和出栈。压栈和出栈只能在一端进行,所以需要用堆栈指针寄存器 SP(Stack Pointer)来指示下一个数据应当压入堆栈内的什么位置,或者数据从哪里出栈。栈的内存结构是从上到下扩展的:
顾名思义,DS是数据段(Data Segment)寄存器,ES是附加段(Extra Segment)寄存器。
通常在使用一些数据操作指令时,默认将DS作为段基址,比如:
MOV [BX],AL
当我们定义好段地址后,每一次 CPU 执行到 [BX] 时,便会默认的从 DS 中取值,并且将取得的值作为段地址,因此,当 [BX] 为 0001H 时,CPU 会从 DS 中取得一个 1000H ,由这两个一合成即可以得到正确的物理地址 1000H:0001H 。
注意:8086 CPU 不支持直接将一个数据送入段寄存器中,一般是先将数据送入通用寄存器,再由通用寄存器送入段寄存器。
标志寄存器设计为16位,实际使用9位:
其中6位用以存放算术逻辑单元运算后的结果特征,称为状态标志;
另外3位通过人为设置,用以控制8086的三种特定操作,称为控制标志。
6个状态标志位定义如下:
位置 | 英文 | 中文 | 作用 |
---|---|---|---|
D0 | CF(Carry FLag) | 进位标志 | 用于反映运算是否产生进位或借位。如果运算结果的最高位产生一个进位或借位,则CF置1,否则置0。运算结果的最高位包括字操作的第15位和字节操作的第7位。移位指令也会将操作数的最高位或最低位移入CF。 |
D2 | PF(Parity FLag) | 奇偶标志 | 用于反映运算结果低8位中“1”的个数。“1”的个数为偶数,则PF置1,否则置0。 |
D4 | AF(Auxiliary Carry FLag) | 辅助进位标志 | 算数操作结果的第三位(从0开始计数)如果产生了进位或者借位则将其置为1,否则置为0,常在BCD(binary-codedecimal)算术运算中被使用。 |
D6 | ZF(Zero FLag) | 零标志 | 用于判断结果是否为0。运算结果0,ZF置1,否则置0。 |
D7 | SF(Sign FLag) | 符号标志 | 用于反映运算结果的符号,运算结果为负,SF置1,否则置0。因为有符号数采用补码的形式表示,所以SF与运算结果的最高位相同。 |
D11 | OF(OverFlow FLag) | 溢出标志 | 反映有符号数加减运算是否溢出。如果运算结果超过了8位或者16位有符号数的表示范围,则OF置1,否则置0。 |
3个控制标志位定义如下:
位置 | 英文 | 中文 | 作用 |
---|---|---|---|
D8 | TF(Trap FLag) | 跟踪/陷阱/单步标志 | 当TF被设置为1时,CPU进入单步模式,所谓单步模式就是CPU在每执行一步指令后都产生一个单步中断。主要用于程序的调试。8086/8088中没有专门用来置位和清零TF的命令,需要用其他办法。 |
D9 | IF(Interrupt-Enable FLag) | 中断标志 | 决定CPU是否响应外部可屏蔽中断请求。IF为1时,CPU允许响应外部的可屏蔽中断请求。 |
D11 | DF(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