3. x86架构基础:实模式、保护模式、长模式,段页式内存管理,寄存器组,中断与异常机制
x86架构自1978年诞生以来,经历了从16位到64位的多次重大演进,但其核心执行模型始终围绕三种运行模式展开:实模式(Real Mode)、保护模式(Protected Mode)和长模式(Long Mode)。作为固件开发者,理解这三种模式是编写BIOS/UEFI固件、进行底层硬件初始化和操作系统引导的前提。本章将系统梳理三种模式的内存寻址机制、段页式内存管理、寄存器组结构以及中断与异常处理框架,为后续的固件开发实践奠定坚实的架构基础。
上图展示了x86架构三种运行模式的核心特征及其内存管理、寄存器组和中断机制的总体框架。下面我们将逐一深入剖析每个子系统的原理与实现细节。
3.1 实模式:BIOS的起点
3.1.1 核心原理
实模式是x86处理器上电复位后的初始运行模式,也是BIOS固件最早执行的模式。在实模式下,处理器采用16位段:偏移寻址方式,物理地址计算公式为:物理地址 = 段基址 × 16 + 偏移地址。由于段寄存器和偏移寄存器均为16位,因此最大寻址空间为 2^20 = 1MB。这种寻址方式的本质是将20位物理地址拆分为16位段基址和16位偏移的组合,段与段之间可以重叠,每个段的最大长度为64KB。
实模式下的中断机制依赖位于内存起始地址 0x00000 处的中断向量表(IVT),该表包含256个中断向量,每个向量占4字节(段:偏移),共1KB。BIOS利用这一机制提供INT 10h(视频服务)、INT 13h(磁盘服务)、INT 15h(系统服务)等经典中断调用接口。
3.1.2 判断方法与操作流程
在固件开发中,判断当前是否处于实模式,可以通过读取CR0寄存器的PE位(第0位)来实现:
; 检查是否处于实模式
mov eax, cr0
test eax, 1 ; 测试PE位
jz real_mode ; PE=0 → 实模式
jnz protected_mode ; PE=1 → 保护模式
从实模式切换到保护模式的标准流程包括:
- 关闭中断(CLI)并加载全局描述符表(GDT)
- 设置CR0的PE位为1
- 执行远跳转(Far Jump)刷新指令流水线
- 加载新的段选择子到所有段寄存器
- 设置堆栈段(SS:ESP)
3.1.3 案例拆解:BIOS复位入口
当处理器复位后,CS段寄存器被设置为0xF000,IP被设置为0xFFF0,因此第一条指令的物理地址为 0xF000 × 16 + 0xFFF0 = 0xFFFF0。这个地址位于BIOS ROM的末尾附近,通常存放一条跳转指令,跳转到BIOS真正的POST(加电自检)代码入口。这一设计保证了BIOS固件在系统启动时能够立即获得控制权。
-m16 选项生成16位代码,并合理规划段布局。
3.1.4 常见误区与总结
- 误区一:认为实模式只能访问1MB内存。实际上通过
A20地址线的开启,实模式可以访问到1MB以上的内存(HMA区域),但需要特殊处理。 - 误区二:忽略中断向量表的对齐要求。IVT必须位于物理地址0x00000处,且每个向量必须按段:偏移格式填写。
- 总结:实模式是BIOS固件的起点,理解其寻址模型和中断机制是掌握后续保护模式切换的基础。
3.2 保护模式:32位时代的核心
3.2.1 核心原理
保护模式是x86架构从16位向32位过渡的关键模式,它引入了两个革命性特性:段描述符机制和特权级保护。在保护模式下,段寄存器不再直接存储段基址,而是存储一个16位的段选择子(Segment Selector),通过该选择子索引全局描述符表(GDT)或局部描述符表(LDT)中的段描述符,从而获得段的基址、界限和访问权限属性。
保护模式支持4级特权级(Ring 0到Ring 3),操作系统内核运行在Ring 0(最高特权级),应用程序运行在Ring 3(最低特权级)。这种分级机制有效防止了用户程序对系统关键资源的非法访问。此外,保护模式还支持分页机制,允许操作系统实现虚拟内存管理。
3.2.2 段描述符与选择子结构
| 字段 | 位宽 | 说明 |
|---|---|---|
| 段基址 (Base) | 32位 | 段的起始线性地址 |
| 段界限 (Limit) | 20位 | 段的最大偏移量(以字节或页为单位) |
| 类型 (Type) | 4位 | 代码/数据段、可读/可写、可执行等属性 |
| DPL | 2位 | 描述符特权级 (0-3) |
| P | 1位 | 段是否存在于内存中 |
| D/B | 1位 | 默认操作数大小 (0=16位, 1=32位) |
| G | 1位 | 粒度 (0=字节, 1=4KB页) |
段选择子的结构为:高13位为GDT/LDT中的索引值,第2位为TI位(0=GDT,1=LDT),低2位为请求特权级RPL。
3.2.3 案例拆解:从实模式切换到保护模式
以下是一个典型的保护模式切换代码片段,展示了GDT加载和CR0设置的关键步骤:
; 加载GDT
lgdt [gdt_ptr]
; 切换到保护模式
mov eax, cr0
or eax, 1
mov cr0, eax
; 远跳转刷新流水线
jmp 0x08:protected_mode_entry
protected_mode_entry:
; 加载数据段选择子
mov ax, 0x10
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
mov ss, ax
; 设置堆栈
mov esp, 0x7C00
在这个案例中,GDT中定义了代码段(选择子0x08)和数据段(选择子0x10),均覆盖4GB线性地址空间。切换完成后,处理器进入32位保护模式,可以访问全部4GB内存。
3.2.4 常见误区与总结
- 误区一:认为保护模式必须开启分页。实际上,保护模式允许分段而不分页,此时线性地址直接映射到物理地址。
- 误区二:忽略段寄存器的隐藏部分。每个段寄存器除了可见的16位选择子外,还有不可见的描述符缓存(基址、界限、属性),修改选择子后必须重新加载段寄存器。
- 总结:保护模式是x86架构的基石,其段描述符和特权级机制为现代操作系统提供了硬件级的内存保护和隔离能力。
3.3 长模式:64位计算时代
3.3.1 核心原理
长模式(Long Mode)是x86-64架构引入的64位运行模式,由AMD首先提出,Intel随后在EM64T中实现。长模式包含两个子模式:64位模式和兼容模式。在64位模式下,处理器使用64位线性地址,通用寄存器扩展为64位(RAX-RSP),并新增R8-R15共8个通用寄存器。段寄存器的作用被大幅简化,CS、DS、ES、SS的基址被强制为0,FS和GS仍然保留基址功能,用于实现线程局部存储(TLS)。
长模式强制启用分页机制,且必须使用PAE(物理地址扩展)分页格式。线性地址被划分为四级页表结构:PML4(页映射表)、PDPT(页目录指针表)、PD(页目录)和PT(页表),每级表包含512个8字节条目,支持4KB、2MB和1GB三种页面大小。
3.3.2 模式切换流程
从保护模式切换到长模式需要遵循严格的顺序:
- 关闭分页(CR0.PG=0)
- 设置CR4.PAE=1
- 加载CR3指向PML4表
- 设置EFER.LME=1(启用长模式)
- 设置CR0.PG=1(开启分页,同时进入长模式)
- 使用兼容模式代码段跳转到64位代码段
3.3.3 案例拆解:进入长模式的代码实现
; 进入长模式的关键步骤
mov eax, cr4
bts eax, 5 ; 设置PAE位
mov cr4, eax
; 加载PML4基址
mov eax, pml4_table
mov cr3, eax
; 启用长模式
mov ecx, 0xC0000080 ; EFER MSR
rdmsr
bts eax, 8 ; 设置LME位
wrmsr
; 开启分页进入长模式
mov eax, cr0
bts eax, 31 ; 设置PG位
mov cr0, eax
; 远跳转到64位代码段
jmp 0x08:long_mode_entry
在64位模式下,地址计算不再使用段基址,而是直接使用线性地址。例如,mov rax, [rbx+rcx*8] 中的有效地址直接作为线性地址使用。
3.3.4 常见误区与总结
- 误区一:认为长模式可以运行16位实模式代码。实际上,长模式不支持实模式,只能通过切换到兼容模式运行32位代码。
- 误区二:忽略地址空间规范。x86-64架构只实现了48位或52位线性地址,高16位必须为符号扩展,否则会产生#GP异常。
- 总结:长模式是x86架构的最终形态,理解其分页结构和模式切换流程是开发64位UEFI固件的基础。
3.4 中断与异常机制
3.4.1 核心原理
中断和异常是处理器响应异步事件和同步错误的核心机制。在x86架构中,中断描述符表(IDT)取代了实模式的中断向量表,每个描述符包含处理程序的入口地址、段选择子和属性。IDT可以位于内存任意位置,通过LIDT指令加载。
中断分为两类:外部中断(由硬件设备触发,如定时器、键盘)和软件中断(由INT指令触发)。异常分为三类:故障(Fault)(可恢复,如缺页)、陷阱(Trap)(执行完指令后触发,如调试断点)和中止(Abort)(不可恢复,如硬件错误)。
3.4.2 常见异常向量表
| 向量号 | 名称 | 类型 | 错误码 | 典型触发场景 |
|---|---|---|---|---|
| 0 | #DE 除法错误 | Fault | 无 | DIV/IDIV指令除零 |
| 6 | #UD 非法指令 | Fault | 无 | 执行未定义指令 |
| 8 | #DF 双重故障 | Abort | 无 | 处理异常时又发生异常 |
| 13 | #GP 保护错误 | Fault | 有 | 特权级违规、越界访问 |
| 14 | #PF 页错误 | Fault | 有 | 访问无效页表项 |
| 32-255 | 用户自定义中断 | 中断 | 无 | 硬件设备中断、系统调用 |
3.4.3 案例拆解:页错误处理流程
页错误(#PF)是操作系统和固件开发中最常见的异常之一。当处理器访问一个页表项中P位为0的页面时,会触发#PF异常。异常处理程序通过读取CR2寄存器获取触发异常的线性地址,然后根据错误码判断访问类型:
; 页错误处理程序入口
page_fault_handler:
; 保存上下文
push rax
mov rax, cr2 ; 获取触发异常的线性地址
; 分析错误码(在栈上)
; 位0: P=0页面不存在, P=1保护违规
; 位1: W=0读操作, W=1写操作
; 位2: U=0内核态, U=1用户态
; 根据错误码决定处理策略
; 如果是缺页,则分配物理页并更新页表
; 如果是保护违规,则终止进程
pop rax
iretq
在固件开发中,页错误处理通常用于实现内存映射I/O的按需映射,或者诊断无效的内存访问。
iretq指令返回(64位模式),而不是ret。同时,如果处理程序修改了通用寄存器,必须完整保存和恢复所有被修改的寄存器,否则会导致系统状态不一致。
3.4.4 常见误区与总结
- 误区一:混淆中断和异常的区别。中断是异步的,可能在任何指令边界发生;异常是同步的,由当前指令触发。
- 误区二:在中断处理程序中使用非可重入函数。中断处理程序可能嵌套执行,必须使用可重入代码或禁用中断。
- 总结:中断与异常机制是操作系统和固件响应事件的核心,正确配置IDT和处理程序是系统稳定运行的关键。
3.5 三种模式对比与固件开发实践
| 特性 | 实模式 | 保护模式 | 长模式 |
|---|---|---|---|
| 地址宽度 | 20位物理 | 32位线性 | 64位线性 |
| 最大内存 | 1MB | 4GB | 2^64(实际48/52位) |
| 分段机制 | 段:偏移 | 段描述符 | 段基址强制为0 |
| 分页机制 | 不支持 | 可选 | 强制启用 |
| 特权级 | 无 | 4级 (Ring 0-3) | 4级 (Ring 0-3) |
| 通用寄存器 | 16位 (AX-DX) | 32位 (EAX-EDX) | 64位 (RAX-R15) |
| 中断表 | IVT (固定0地址) | IDT (可重定位) | IDT (可重定位) |
| 固件应用 | BIOS POST早期 | BIOS运行时、UEFI DXE | UEFI 64位、OS加载器 |
在固件开发实践中,模式选择取决于具体阶段的需求。BIOS的POST阶段在实模式下完成基本硬件初始化,然后切换到保护模式执行运行时服务;UEFI固件则通常在SEC阶段进入保护模式,在DXE阶段切换到长模式(64位平台)。理解各模式的能力边界和切换代价,有助于设计高效的固件架构。
- 实模式使用段:偏移寻址,最大1MB,是BIOS的起点
- 保护模式引入段描述符和特权级,支持分页,最大4GB
- 长模式强制分页,使用四级页表,支持64位寻址
- 中断与异常通过IDT分发,页错误处理是内存管理的关键
- 模式切换需要严格遵循操作顺序,任何错误都可能导致系统崩溃