IA32体系结构

Published: 5/4/2024

80386 是Intel于1985年推出的一款32位微处理器,是Intel 80286的后继产品。80386是Intel首款支持32位操作系统的微处理器,同时也是Intel支持虚拟内存的微处理器。IA-32架构则是Intel基于I386提出的32位架构的微处理器架构的统称,并在历史进程中不断发展变化,但其大体内容仍然保持不变,下面以I386为例进行介绍。

执行模式

80386总共有3种执行模式,分别是:

  • 实模式(Real Address mode): 机器初始化后会进入该模式,被看作是8086的拓展,带有新的指令。大多数情况下,都只会将实模式用于初始化。

  • 保护模式(Protect mode):

  • V86模式(Virtual 8086 mode):

根据系统寄存器CR0PE位(Protection Enable,bit 0)与EFLAGS中的VM位(Virtual Machine,bit 15)来表示当前处于哪种模式。当PE=0时机器处于实模式,当PE=1时机器处于保护模式。当VM=1,并且PE=1时机器处于V86模式,如果PE=0,VM位无效,也就是说V86模式基于保护模式。

描述符

80386使用描述符来对资源进行管理。

386总共有3个和描述符相关的寄存器,分别是:GDTR(全局描述符表寄存器),LDTR(局部描述符表寄存器),IDTR(中断描述符表寄存器)。

保护机制

80386具有一套完善的特权级与保护机制,对内存访问,I/O访问,中断处理等进行了权限管理,这一保护机制由硬件实现,并不会造成性能损失。

段保护

当段选择器被载入某一段寄存器中时,CPU会检查段描述符中保护域中的参数是否合法。

image-20240805120905866

其中的TYPE域用于区分不同的段以及其用法。DATA段与EXECUTABLE段描述符常被用户程序使用,TYPE最高位为1,SYSTEM段描述符TYPE最高位为0,被操作系统使用。 TYPE域其他位标注了该段的权限。需要注意一些规则:

  • 只有可执行段能被载入CS寄存器,只有可写段能被载入SS段,只有可读段能被载入4个数据段的寄存器(至少包含哈)。

特权级

80386中对于对象特权级的取值范围为0-3,0表示最高特权级,使用特权级的对象主要包括:

  • 存在DPL(Descriptor privilege level)位的描述符

  • 存在RPL(requestor’s privilege level)位的选择器,用于表示发起该选择器的程序所处特权级

  • 存在CPL(Current privilege level)的系统寄存器

如之前所说,CPU会自动比对CPL域与其他的特权级进行检查。数据访问操作合法当且仅当数据的DPL大于等于CPL与RPL的最大值。

控制流

80386的控制流主要通过指令与异常两部分改变,指令部分包括JMP,CALL,RET,INT,IRET。 跳转指令主要分为FARNEAR两种模式,NEAR模式只会在段内跳转,所以CPU只会对段地址范围与跳转地址合法性进行检查。FAR模式会跨越段与段,所以CPU会在这种类型的跳转指令执行时进行特权级检查,该跳转指令合法当且仅当DPL=CPL或者目标代码段描述符中的Conforming位置位1,并且DPL小于等于CPL。(也就是说验证过了的或者更低特权的段可以跳转)

段描述符中Conforming bit置位1表示该段被验证过,主要用于高特权级使用低特权级所共享的代码。需要注意的是,当前往验证段时,CPL并不会改变。

如果前往未验证段并且该段的DPL表示的特权级高于CPL,则需要使用CALL GATE描述符与CALL指令进行跳转,不能使用JMP指令。

Gate

Gate descriptor主要用于实现不同特权级之间控制流的切换,主要包括:Call gates,Task gates,Interrupt gates,Trap gates

Call Gates

Call Gates主要用于定义程序的入口以及入口的特权级。 当使用Call gates进行跳转时,需要额外考虑其特权级。Call Gates的DPL决定了能前往该段的特权级,CPL必须小于等于Call Gates的DPL,并且当CPL小于该DPL时,必须只能是CALL指令。

每个特权级都有自己独立的栈,并且确保高特权栈中为被验证低特权程序保留有足够的空间。

页面保护

80386同样使用页表进行页面保护,当页表项中U/S为0时,。当R/W为0时,页面只读,否则可读写;当P为0时,页面不存在。

IO

80386对于IO同样具有特权级保护机制。其中IO主要分为Port和Memory mapped两种方式。

系统寄存器中EFLAGS中的IOPL位表示使用IO指令的权限,只有当CPL <= IOPL时才能执行IO指令,并且IOPL只有在特权级0才能通过POPF改变。

TSS段中IO权限map则定义了使用IO port的权限。

需要注意的是,上面的机制只会在保护模式起作用,在实模式并不会对IO进行保护。

启动流程

BIOS

BIOS位于ROM中,在机器上电后由硬件负责载入RAM,首先跳转到0xFFF0处执行长跳转指令到BIOS的入口地址,然后执行机器自检与硬件初始化等工作;在这一过程结束后,BIOS会尝试寻找包含引导扇区的设备,通过检查设备第一个扇区的最后两个字节是否为0x55AA来判断是否为引导扇区,如果找到就会将该扇区内容复制到0x7C00处并跳转到该地址执行启动代码。

值得注意的是,BIOS运行时机器会处于16位的保护模式。

主引导扇区

下面是对主引导扇区组成的详细介绍。

启动盘制作

寄存器

通用寄存器

I386拥有8个32位GPR,分别是EAXEBXECXEDXESIEDIEBPESP。这些寄存器可以分为两类,分别是数据寄存器(前四个)和指针寄存器

4个数据寄存器可分为低16位的AXBXCXDX,对于这四个16位寄存器,可以分别访问其低8位和高8位,分别为ALAHBLBHCLCHDLDH。后面4个指针寄存器不可再分。

需要注意,虽然它们被称为通用寄存器,在实际代码中,它们都具有一定的规定用途(强制必须使用)。

段寄存器

段寄存器共有6个,分别是CSDSSSESFSGS。这些寄存器用于存储段地址,上图已经标注了每个段寄存器中所存放段的用途。

标志寄存器与指令寄存器

I386中的指令寄存器为32位的EIP用于存放下一条指令的地址,标志寄存器为32位的EFLAGS,其中的每一位都有特定的含义,下面是EFLAGS的结构:

X86中的条件指令以及指令触发的异常都与标志寄存器有关,因此在编写汇编代码时,需要注意标志寄存器的状态。

系统寄存器

I386中的系统寄存器类型主要有:

  • 标志寄存器

  • 内存管理寄存器

  • 控制寄存器

  • 调试寄存器

  • 测试寄存器

标志寄存器

标志寄存器存放了表示执行模式,中断与异常,IO权限等信息的标志位。

内存管理寄存器

主要包括了:

  • GDTR: 全局描述符表寄存器

  • LDTR: 局部描述符表寄存器

上面的两个寄存器指向了GDT和LDT的地址。

  • IDTR: 中断描述符表寄存器

IDTR指向了中断处理函数的入口地址。

  • TR: 任务寄存器

TR负责被CPU用于标识当前任务。

控制寄存器

主要包括了:CR0, CR2, CR3

调试寄存器

主要提供了断点寄存器,能够不修改代码段就能够在指定地址设置断点。

系统指令

任务相关

  • LTR: 读TR

  • STR: 写TR

地址相关

  • LLDT: 读LDT

  • SLDT: 写LDT

  • LGDT: 读GDT

  • SGDT: 写GDT

验证指针

  • ARPL: 调整RPL

  • VERR: 验证读

  • VERW: 验证写

  • LAR : 读访问权限

  • LSL : 读段限制

IO相关

  • IN: 读端口

  • OUT: 写端口

  • INS: 读端口串

  • OUTS: 写端口串

中断相关

  • CLI: 关中断

  • STI: 开中断

  • LIDT : 读IDT

  • SIDT : 写IDT

控制相关

  • HLT : 停CPU

指令格式

待补充。

内存管理

内存模型

I386支持4GB大小的物理内存空间,支持两类内存模型:

  • Flat Model: 最多4GB的线性地址空间

  • Segmented Model: 最多由16383个线性地址空间组成的段地址空间,其中每个线性地址空间最多4GB大小。

对于Segmented Model,地址主要由段选择器段内偏移两部分组成。

数据类型

I386中的基本数据类型有字节双字。需要注意I386手册中提出字与双字的地址并不需要对齐其数据大小,如果使用RUST等对数据对齐有一定要求的语言,需要注意这一点。

在I386中同样需要注意数据的大小端,I386是小端模式,对于某些文件系统或者网络协议需要注意处理好字节序,下面的图片很好地展示了I386的字与双字的布局。

除了上面的类型以外,I386还支持BCD(Binary Coded Decimal)类型,BCD类型是一种用二进制编码十进制数的方式,可以用来进行十进制数的运算。BCD码又分为未压缩BCD码和压缩BCD码。

地址转换

I386中的地址转换主要由逻辑地址(由16位段选择器和32位段内偏移两部分组成)转换为线性地址,线性地址转换为物理地址两部分组成。下图是I386中地址转换的示意图。

从上图也可以看到I386中逻辑地址,线性地址与物理地址的组成。

段翻译

段翻译负责将逻辑地址(段地址:段内偏移)翻译成线性地址,主要涉及到:

  • 描述符

  • 描述符表

  • 段选择器

  • 段寄存器

下图是该过程的具体表述:

从图中可以看到,根据段选择器从段描述符表中选取对应段描述符,得到其基地址并与段内偏移相加便得到了其线性地址。

描述符

描述符负责为CPU提供用于段地址翻译的数据。描述符通常由编译器与操作系统等系统软件创建,主要组成如下:

  • BASE: 主要用于定位4GB线性地址中段的位置,CPU将3个BASE 组合成32位值

  • LIMIT : 定义段大小,如果标志位中Granularity位为1,则单位为4KB,否则为1B

  • TYPE : 用于区分不同种类的描述符

  • DPL : 描述符特权级,用于实现保护机制

  • Segment present bit:用于表示描述符是否有效

  • Access bit : 是否被访问过

描述符表

描述符可被存放于GDT(全局描述符表)或者某个LDT(局部描述符表)中。 描述符表只是存放于内存之中的8字节entry组成的数组,如下图所示:

段选择器

段选择器用于寻找对应的段描述符,其组成如下:

  • Index: 表示描述符在表中的下标

  • Table Indicator : 表明描述符表种类(0位GDT,1为当前LDT)

  • Requested Privilege level : 用于保护机制

段寄存器

页翻译

从前面可以得知,线性地址由DIR,PAGE,OFFSET三部分组成,页翻译使用两级页表将线性地址转化为物理地址。这一部分其实是可选的,当CR0寄存器中的PG位为1时才会开启,并且I386使用4K物理页框。 翻译时I386使用DIR域寻找一级页表中的页表项,使用PAGE寻找二级页表中的页表项进而得到其物理页,最后使用页内偏移得到其物理地址。

下面是页翻译的具体流程:

在I386中,每个页表占据一页物理内存,及4KB,并且每个页表项4B,故有1K个页表项。当前页表的物理地址被存放在CR3中,下面是页表项的组成:

Cache与TLB

异常与中断

上面是I386中的中断向量表,I386中的中断分为中断异常两种,I386将中断视为异步异常,而将异常视为同步异常,中断是外部设备发起,而异常由CPU内部发起。

IO

I386中的IO主要有两种方式:

  • Port-Mapped IO: 通过端口号来访问IO设备(特定的IO指令)

  • Memory-Mapped IO: 通过内存地址来访问IO设备(普通的内存读写指令)

端口IO

80386总共支持最多16个外部设备,每个设备可拥有一个64K大小的地址空间。可被看作是8位寄存器,16位寄存器,32位寄存器的数据形式进行访问。需要注意地址对齐以便能够作为总线地址。

使用INTOUT指令进行端口IO间的数据传输,使用INTSINS指令进行数据流IO数据传输。 在使用INTOUT时使用的GPR是EAS,AX与AL。而在使用INSOUTS时使用的GPR是ES,ESI(地址由ES:EDI组成)。

内存映射IO

使用普通的内存读写指令进行IO设备的访问,并且这些地址和普通的内存地址一样,都会受到I386的保护。

TASK

80386支持多道程序。