x86 ,她来了

X86 寄存器总结

首先来认识一下 INTEL 体系下的 x86 指令集的相关知识。

x86 是 intel 公司开发的一种 32 位指令集,自从 80386 开始一直沿用至今,它是一种复杂指令集的架构,intel 官方把这种指令集叫做 “IA-32” 。

X86_64 是一种 64 位指令集,所以我们常说的 x86_64 和 x86 的主要区别就是 32 位和 64 位的问题,这俩指令集体系的指令长度不同。指令集是 CPU 的语言,32 位指令集表示 CPU 一次能够处理 32 位数据,64 位指令集表示 CPU 一次能够处理 64 位的数据,更为高效。

这个 32 位和 64 位也表示着我们常说的 32 位操作系统或者 64 位操作系统。

x86 中的 X 表示一个范围,这个范围里面泛指很多指令集型号,后来为了统一,把 x86 就指代的是 32 位指令集。

x86 家族下的指令集主要有

  • 8086、8088 – 16 位寄存器
  • 80186、80286 是两个过度产品
  • 80386、80486 以及后面的各种型号都是 32 位寄存器。

x86 寄存器

熟悉我的小伙伴都知道,我写过几篇汇编语言的文章,而且还在持续更新中,我写的汇编语言就是基于 8086 汇编为前提的,8086 汇编中的寄存器是 16 位的,而 x86 寄存器的位数都是 32 位的,主要分为:

  • 8 个通用寄存器,分别是 EAX、EBX、ECX、EDX、ESI、EDI、ESP、EBP
  • 1 个标志寄存器:EFLAGS
  • 6 个段寄存器:CS、DS、ES、FS、GS、SS
  • 5 个控制寄存器:CR0 – CR4
  • 8 个调试寄存器:DR0 – DR7
  • 4 个系统地址寄存器:GDTR、IDTR、LDTR、TR
  • 其他寄存器:EIP、TSC 等。

通用寄存器

我们在学汇编的时候知道(不过你没学过汇编也不会妨碍,我会尽量平铺直叙些),十六位的寄存器比如 AX、BX、CX、DX 它们都是可以再细分的,如下图所示。

图片

拿 AX 和 AH、AL 来举例子,我们可以把数据暂存在 AX 中,也可以把数据暂存在 AL 和 AH 中,它们可以单独使用,也可以一起使用。

需要注意的是,暂存在 AX 中的数据,其内部真实情况还是写入了 AL 中,当数据超过 AL 所能暂存的最大值后才会写入到 AH 中,不能把数据直接暂存在 AH 中。AX = AL + AH ,好好体会这个公式,其他三个寄存器类似,就是暂存的数据和指令类型不同。

回到 32 位的 x86 寄存器中来,也是同样的情况,EAX、EBX、ECX、EDX 这四个 32 位寄存器都可以再细分,如下所示:

图片

除了这 4 个 32 位寄存器可以分为 8 位寄存器之外,其他通用寄存器不可再分为 8 位的寄存器(ESI、EDI、ESP、EBP)。

下面来介绍一下这几个寄存器的主要作用:

可再分成 8 位的通用寄存器 – 4 个。

  • EAX:累加器(Accumulator),它的低 16 位是 AX,AX 可以再细分为 AH 和 AL。EAX 是很多乘除法的默认寄存器,比如我们学 8086 的时候,被除数就会默认放在 AX 中或者 AX 和 DX 中。EAX 同理。还可以存放函数的返回值、存放段的偏移地址等。
  • EBX:基地址寄存器(Base Register),它的低 16 位是 BX ,BX 还可以再细分为 BH 和 BL。主要用于在寻址内存单元时存放基地址。
  • ECX:计数寄存器(Count Register),它的低 16 位是 CX,CX 可以再分为 CH 和 CL。ECX 主要用于在循环指令中控制循环次数;其中 CL 在移位指令中用于指明移位位数。
  • EDX:数据寄存器(Data Register),它的低 16 位是 DX,DX 可以再分为 DH 和 DL。EDX 用于存放乘除法的高位运算结果,也可用于存放 IO 端口地址。

不可再分成 8 位的通用寄存器 – 4 个。

  • ESI/EDI:这俩是一对儿的关系,分别叫做源索引寄存器(Source Index Register)目标索引寄存器(Destination Index Register),它俩只能分成两个 16 位寄存器 SI 和 DI。这俩用于存放寻址的偏移量。它们和 EBX 一起使用可以实现更灵活的内存寻址。ESI 和 EDI 最常用的就是字符串检索,在字符串操作指令中,DS:ESI 指向源字符串,ES:EDI 指向目标字符串。
  • EBP/BSP:分别是基指针寄存器(Base Pointer Register)堆栈指针寄存器(Stack Pointer Register),低 16 位分别是 BP 和 SP,不能再细分。BP 作为通用寄存器,也可存储算术逻辑运算的操作数和运算结果,作为指针寄存器,BP 可以直接存取内存数据。SP 是堆栈指针寄存器,它和 SS 一起指向堆栈的栈顶,并且 SP 只用于访问栈顶。

标志寄存器

EFLAGS 属于状态寄存器,也被称作标志寄存器。状态/标志寄存器对于程序的执行过程和运行结果来说至关重要。EFLAGS 如下图所示。

图片

EFLAGS 中的系统标志和 IOPL 字段用于控制 IO 访问、可屏蔽硬件中断、调试、任务切换以及虚拟 – 8086 模式。其他是一些通用标志,这里有 CF – 进位标志,PF – 恢复标志 ,AF – 辅助进位标志, ZF – 零标志,SF – 负号标志,DF – 方向标志,OF – 溢出标志。其中的第 1、3、5、15、18 – 31 位作为保留位。

通过使用 LAHF/SAHF/PUSHF/POPF/POPFD 等指令,可以将 EFLAGS 寄存器中的标志位成组移到栈中或 EAX 寄存器,或者从内存等位置将结果保存到 EFLAGS 寄存器中。

下面我们对这些标志位进行说明。

  1. 状态标志位(Status Flag)

EFLAGS 寄存器中的 0、2、4、6、7、11 位是算数指令的结果标志,算数指令包括 ADD、SUB、MUL、DIV。这些状态标志位的作用如下:

  • CF(0 位):Carry Flag 无符号运算数的运算结果标识,如果算数操作产生的结果在最高有效位发生进位或者借位,就将其置为 1 。比如对于位数为 N 的无符号数来说,其对应的二进制最高有效位是 N – 1 位,其第 N 位是它的假想最高位。如果最高有效位向更高有效位进位,就表示运算进位,CF = 1,借位同样如此。
  • PF(2 位):Parity flag 奇偶标志位,如果运算结果转换成二进制后,其包含偶数个 1 时,则 PF 位为 1 ,否则为 0。
  • AF(4 位):Adjust flag 辅助进位,如果算术操作在结果的第 3 位发生进位或借位则将该标志置1,否则清零。这个标志在BCD(binary-code decimal)算术运算中被使用。
  • ZF(6 位):Zero flag 零标志位,判断运算结果是否为 0 ,如果为 0,ZF = 1。
  • SF(7 位):Sign flag 负号标志位,判断运算结果的二进制最高有效位是否为 1 – 表示负数,如果是负数,SF = 1。
  • OF(11 位):Overflow flag 溢出标志,有符号数的运算结果标志。

简单来说就是:

算数指令会产生三类结果:无符号整型、有符号整型和 BCD ,BCD 就是用二进制来表示的整型。如果把结果当成是有符号数,那么会影响 OF 标志,产生进位或借位;如果把结果当成无符号数,会影响 CF 标志,产生进位或借位;如果结果是 BCD ,就会影响 AF 、SF、PF、ZF 标志。

  1. 控制标志位(DF-Direction flag)

DF 控制着串传输指令,它是一个方向标志位。在串传输比如 MOVS 指令中,控制每次操作后的 SI DI 的递增或递减。df = 0 表示每次操作后 SI、DI 递增;df = 1 表示每次操作后 SI、DI 递减。cld 指令和 std 指令控制着递增和递减。cld 指令将 df 置 0 ,递增;std 指令将 df 置 1 ,递减。df = 0 可以理解为正向传递;df = 1 为逆向传递。

  1. 系统标志和 IOPL 域(System Flags and IOPL Field)

EFLAGS 寄存器中的这部分标志用于控制操作系统或是执行操作,它们不允许被应用程序所修改。这些标志的作用如下:

  • TF(8 位):Trap flag 跟踪标志,将该位设置为 1 表示允许单步调试操作,清零则不允许单步调试。单步执行过程中,在每个指令执行之后都会产生一个调试异常,这样我们就可以观察每个指令运行后的状态。
  • IF(9 位):Interrupt enable flag 这个标志用于控制处理器对可屏蔽中断请求的响应,IF = 1 表示响应可屏蔽中断;IF = 0 表示禁止可屏蔽中断。
  • IOPL(12-13 位):I/O privilege level field 指示当前运行的 IO 特权级(IO Level),当前运行程序或任务的 CPL 必须小于等于 IOPL 才允许访问。只有当 CPL 特权级为 0 时,程序才能使用 POPF 和 IRET 指令修改这个字段。
  • NT(14 位):Nested Task 嵌套任务标志,它控制着被中断任务和调用任务之间的链接关系。在使用 CALL 指令、中断或者异常执行任务调用时,处理器会设置这个标志。在通过 IRET 从任务返回时,处理器会检查这个 NT 标志,使用 POPF POPFD 也可以修改这个标志。
  • RT(16 位):Resume flag 恢复标志,控制处理器对调试断点的响应。设置这个值时,这个标志会临时禁止断点指令产生的调试异常;当恢复时,断点指令将会产生异常。
  • VM(17 位):Virtual-8086 Mode 标志,设置此位时,就开启虚拟 8086 模式;当复位这个标志时,就回到保护模式下。

段寄存器

段寄存器主要有 6 个,CS、DS、ES、FS、GS、SS,这些段寄存器也被称为 sreg,相对的,reg 是用来表示寄存器的。

段寄存器存在的意义是为了让内存更好的分段来用的,内存地址的访问通过段寄存器 + 段内偏移来实现。这些 CS、DS、ES、FS、GS、SS 就是干这事的,也可以把段寄存器 + 段内偏移来理解成小区 + 房号的组合,小区就是段基址,你家的房号就是段内偏移。这样通过小区和房号就能唯一定位你的位置(内存物理地址),并且这个位置是 16 位的。下面说下这六个兄弟都是哪个小区:

CS:一般用来描述代码段,代码段保存正在执行的指令。处理器从代码段读取指令时,使用有 CS 寄存器中的段选择符与 EIP 寄存器联合构成的逻辑地址。取指执行是计算机一个非常重要的概念。它所表明的含义就是取得 CS:IP 地址(在 x86 体系下就是 EIP,32 位)处的指令来执行。

DS:数据段寄存器,一般用来保存程序的数据结构。

ES/FS/GS:这三个段寄存器可以一起理解,它们指的是额外的数据段,其实也可以和 DS 一起理解。例如,可以创建如下的四个数据段:第一个数据段保存当前程序模块的数据结构,第二个数据段保存更高级别程序模块导出的数据,第三个数据段保存动态创建的数据结构,最后一个数据段保存另一个程序共享出来的数据。

SS:一般用来描述栈段的选择符,这里的栈段用于存储程序和当前执行处理器程序的栈帧,所有在栈上的操作都可以通过 SS:SP 来定位,并且 SS:SP 一定是指向栈顶的。

控制寄存器

控制寄存器有四个,比较简单粗暴,分为 CR0、CR1、CR2、CR3 ,控制寄存器用于确定处理器的操作模式以及当前执行任务的特性,这几个寄存器都是 32 位的,各自的作用如下图所示:

图片

这几个寄存器是与分页机制密切相关,因此,在进程管理及虚拟内存管理中会涉及到这几个寄存器。对控制寄存器的读写是通过 mov 指令来实现。

  • CR0 :含有控制处理器操作模式和状态的系统控制标识,它分为两种,一种是协处理器控制位,一种是保护控制位。先说下协处理器控制位:其中扩展类型位 ET、任务切换位 TS、仿真位 EM 和数学存在位 MP 用于控制 x86 的浮点,也就是数学协处理器的操作。

协处理器是个什么概念呢,它就是一种芯片,用于减轻系统微处理器特定处理任务的芯片。

CR0 的 ET位(标志)用于选择与协处理器进行通信所使用的协议,即指明系统中使用的是 80387 还是 80287 协处理器。TS、MP 和 EM 位用于确定浮点指令或 WAIT 指令是否应该产生一个设备不存在(Device Not Available,DNA)异常。这个异常可用来仅为使用浮点运算的任务保存和恢复浮点寄存器。对于没有使 用浮点运算的任务,这样做可以加快它们之间的切换操作。

保护控制位:

(1)PE:CR0 的 第 0 位是启用保护(Protection Enable)标志。当设置该位时即开启了保护模式;当复位时即进入实地址模式。这个标志仅开启段级保护,而并没有启用分页机制。若要启用分页机制,那么 PE 和 PG 标志都要置位。(2)PG:CR0 的第 31 位是分页(Paging)标志。当设置该位时即开启了分页机制;当复位时则禁止分页机制,此时所有线性地址等同于物理地址。在开启这个标志之前必须已经或者同时开启 PE 标志。即若要启用分页机制,那么 PE 和 PG 标志都要置位。

(3)WP:对于 intel 80486 或以上的 CPU 来说,CR0 第16 位是写保护(Write Protect)标志。当设置这个标志时,会禁止超级用户向用户级只读页面执行写操作;复位时相反。这个标志有助于 UNIX 类系统实现写时复制技术。

(4)对于 intel 80486 或以上的 CPU 来说,CR0 第 5 位是协处理器错误标志(Numeric Error)。

  • CR1 作保留用
  • CR2 和 CR3 用于分页机制,CR2 是页故障线性地址寄存器,保存最后一次出现页故障的全 32 位线性地址。在报告页异常时,处理器会把引起异常的线性地址存放在 CR2 中。因此操作系统中的页异常处理程序可以通过检查 CR2 的内容来确定线性地址空间中哪一个页面引发了异常。
  • CR3 含有存放目录表页面的物理地址,因此该寄存器也被称为页目录基地址寄存器 PDBR(Page-Directory Base address Register)

内存管理寄存器

内存管理器有四个,分别是 GDTR、LDTR、IDTR 和 TR 。

图片

GDTR、LDTR、IDTR 和 TR 都是段基址寄存器,这些寄存器中都包含着分段机制的重要信息。GDTR、LDTR 和 IDTR 用于寻址存放描述符表的段。TR 用于寻址一个特殊的任务状态段 TSS,TSS 段中包含着当前执行任务的重要信息。

因为进入保护模式后,是无法直接寻址到内存段、数据段和栈段的,这些段的信息都被保存在 GDT 全局描述符表中,全局描述符表就是记录各个段信息的表。

  • 全局描述符寄存器 GDTR

GDTR 寄存器用于存储 GDT 全局描述符表中 16 位的表长和 32 位的线性地址。LGDT 和 SGDT 指令分别用于加载和保存 GDTR 寄存器的内容。在机器刚加电或处理器复位后,基地址被默认设置为 0 ,而长度被设置位 0xFFFF。

  • 中断描述符寄存器 IDTR

和 GDTR 类似,只不过它是用来保存中段描述符表 IDT 的寄存器,使用 LIDT 和 SIDT 用于加载和保存 IDTR 寄存器的内容。在机器刚加电或处理器复位后,基地址被默认设置为 0 ,而长度被设置位 0xFFFF。

  • 局部描述符表 LDTR

LDTR 用于存放局部描述符表 LDT 的 32 位线性基地址、16 位段限长和描述符属性值。指令 LLDT 和 SLDT 分别用于加载和保存 LDTR 寄存器描述符的部分。包含 LDT 表的段必须在 GDT 表中有一个段描述符项。

  • 任务寄存器 TR

TR 寄存器用于存放当前任务 TSS 段的 16 位段选择符、32 位基地址、16 位段长度和描述属性值。它引用 GDT 表中一个 TSS 类型的描述符。指令 LTR 和 STR 分别用于加载和保存 TR 寄存器的段选择符部分。

其他寄存器

EIP:指令的偏移地址。其本质上并不能直接被指令访问。这个寄存器指令由控制转移指令、中断及异常所控制。读操作通过执行 call 指令并取得栈中所存放的地址来实现,而写操作则通过修改程序栈中的返回指令指针并执行RET/IRET 指令来完成,因此尽管这个寄存器相当重要,但其实并不是操作系统在实现过程中所需关注的焦点。

TSC:(时间戳寄存器)每个时钟周期时其值加 1,重启时清零。

浮点寄存器:由于在 80486 微处理器内部设有浮点运算器,因此在其内部有相应的寄存器,其中包括 8 个 80 位通用数据寄存器、1 个 48 位指令指针寄存器、1 个 48 位数据指针寄存器、1 个 16 位控制字寄存器、1个 16 位状态字寄存器和 1 个 16 位标记字寄存器。

系统指令

系统指令用于处理系统级命令,比如加载系统寄存器、中断。大多数指令只能处于特权级为 0 的操作系统软件执行。

这里需要说明一下,操作系统操作是有等级权限的,不是应用程序在何时何地都可以访问或者操作内核的,特权级有 4 个 level,分别为 0 ~ 3。

操作系统位于 0 级特权,可以直接控制硬件,掌控各种核心数据;系统程序分别位于 1 级特权或 2 级特权,主要是一些虚拟机、驱动程序等系统服务;而一般的应用程序运行在 3 级特权。

下面列出了一些操作系统指令,并且还提到了是否受到保护:

图片

我后面打算在知识星球上带领大家学习操作系统、计算机网络、汇编、Java、C/C++ 等相关知识,能学到非常多的知识,打好计算机基础功底,在这个艰难的环境下有立足之地!
详情可了解 我的知识星球

声明:文中观点不代表本站立场。本文传送门:https://eyangzhen.com/63579.html

联系我们
联系我们
分享本页
返回顶部