3. 保护模式与分页

在 x86 架构中,从实模式到保护模式的切换是启用分页机制的第一步。而 CR0 寄存器 是控制处理器运行模式与内存管理策略的核心控制寄存器。本章将深入剖析 CR0 中的 PE 位与 PG 位,明确开启分页的完整前提条件与操作流程。

3.1 CR0 寄存器结构

CR0(Control Register 0)是 x86 架构中最重要的控制寄存器之一,其位宽为 32 位(IA-32)或 64 位(x86-64)。它控制着处理器的运行模式、缓存策略、对齐检查以及分页使能等关键功能。

关键概念: CR0 寄存器中的 PE 位(位 0)和 PG 位(位 31)是开启分页的“总开关”。PE 位启用保护模式,PG 位启用分页机制。两者必须按特定顺序设置,否则会导致处理器异常。

下表列出了 CR0 寄存器中与分页和保护模式直接相关的关键位:

位域 名称 功能描述 与分页关系
位 0 PE (Protection Enable) 启用保护模式。置 1 时处理器进入保护模式,支持段保护与特权级。 分页的前提条件,必须在 PG 位之前设置。
位 31 PG (Paging Enable) 启用分页机制。置 1 时处理器使用页表进行地址转换。 分页的直接使能位,必须在 PE 位已置 1 且页表已初始化后设置。
位 16 WP (Write Protect) 写保护。置 1 时禁止超级用户写入用户级只读页。 影响分页的写权限检查逻辑。
位 4 ET (Extension Type) 协处理器类型指示。通常固定为 1。 与分页无直接关系。
位 5 NE (Numeric Error) 浮点单元错误报告方式。 与分页无直接关系。

3.2 PE 位:保护模式使能

PE 位是 CR0 寄存器的第 0 位。当 PE = 0 时,处理器运行在实模式;当 PE = 1 时,处理器进入保护模式。保护模式的核心特征包括:

  • 段式内存管理: 使用段描述符和全局描述符表(GDT)来定义段的基址、限长和访问权限。
  • 特权级保护: 支持 4 个特权级(Ring 0 ~ Ring 3),防止低特权级代码访问高特权级数据。
  • 32 位地址空间: 线性地址空间从实模式的 1MB 扩展到 4GB。
实战提示: 在设置 PE 位之前,必须确保已经正确初始化了 GDT(全局描述符表)和至少一个代码段描述符与数据段描述符。否则,设置 PE 位后处理器将立即因段描述符无效而触发三重故障(Triple Fault)。

设置 PE 位的典型汇编代码片段如下:

; 假设 GDT 已加载到 GDTR
mov eax, cr0
or eax, 0x1        ; 设置 PE 位(位 0)
mov cr0, eax       ; 写入 CR0,进入保护模式

; 立即执行远跳转以刷新指令流水线
jmp 0x08:prot_mode_entry

prot_mode_entry:
; 现在运行在 32 位保护模式下

3.3 PG 位:分页使能

PG 位是 CR0 寄存器的第 31 位。当 PG = 1 时,处理器启用分页机制,所有线性地址必须通过页表转换为物理地址。分页机制的核心特征包括:

  • 页式内存管理: 将线性地址空间划分为固定大小的页(通常为 4KB),通过多级页表映射到物理页框。
  • 页级保护: 每个页表项(PTE)包含用户/超级用户位(U/S)和读/写位(R/W),实现页级访问控制。
  • 虚拟内存支持: 分页是实现虚拟内存、按需调页和页面交换的基础。
重要约束: 设置 PG 位时,PE 位必须已经为 1。如果尝试在 PE = 0 的情况下设置 PG = 1,处理器会触发通用保护异常(#GP)。此外,在设置 PG 位之前,必须确保 CR3 寄存器已经指向一个有效的页目录基址。

设置 PG 位的典型汇编代码片段如下:

; 假设页目录已初始化,其物理地址已加载到 CR3
mov eax, cr0
or eax, 0x80000001 ; 同时设置 PE 位(位 0)和 PG 位(位 31)
mov cr0, eax       ; 写入 CR0,同时启用保护模式和分页

; 立即执行远跳转以刷新流水线
jmp 0x08:paging_enabled_entry

paging_enabled_entry:
; 现在运行在保护模式 + 分页模式下

3.4 开启分页的前提条件

开启分页并非简单地设置一个位,而是一个需要严格遵循步骤的过程。以下是开启分页的完整前提条件清单:

  1. 初始化 GDT: 创建并加载全局描述符表,至少包含一个代码段描述符和一个数据段描述符。
  2. 设置 PE 位: 将 CR0.PE 置 1,使处理器进入保护模式。
  3. 初始化页表结构: 创建页目录和页表,确保线性地址到物理地址的映射正确。对于 4 级页表,需要初始化 PML4、PDPT、PD 和 PT。
  4. 设置 CR3 寄存器: 将页目录(或 PML4)的物理基址加载到 CR3 寄存器。
  5. 设置 PG 位: 在 PE = 1 且 CR3 有效的前提下,将 CR0.PG 置 1。
  6. 刷新 TLB: 设置 PG 位后,处理器会自动刷新 TLB,但建议通过远跳转或特权级切换来确保流水线一致性。

以下 SVG 图展示了从实模式到保护模式+分页模式的完整状态转换流程:

开启分页的完整流程 实模式 PE=0, PG=0 初始化 GDT 加载段描述符 设置 PE 位 CR0.PE = 1 初始化页表 PML4 → PDPT → PD → PT 设置 CR3 加载页目录基址 设置 PG 位 CR0.PG = 1 分页模式 PE=1, PG=1 关键约束 PG 位必须在 PE=1 时设置 CR3 必须在设置 PG 前有效

3.5 设置 PE 与 PG 的常见陷阱

在实际开发中,设置 PE 和 PG 位时容易遇到以下问题:

陷阱场景 原因 后果 解决方案
PE=0 时设置 PG=1 违反 x86 架构规范 触发 #GP 异常 先设置 PE,再设置 PG
设置 PG 前 CR3 无效 CR3 指向未初始化的内存 页表遍历失败,触发 #PF 异常 确保页表已初始化,CR3 指向有效物理地址
GDT 未初始化就设置 PE 段描述符无效 三重故障,系统重启 先加载 GDT,再设置 PE
设置 PG 后未刷新 TLB TLB 中缓存了旧映射 地址转换错误,数据访问异常 执行远跳转或写 CR3 刷新 TLB
页表映射未覆盖当前 EIP 当前执行地址未映射 立即触发 #PF 异常 确保页表包含当前代码段的恒等映射
最佳实践: 在设置 PG 位之前,建议先建立恒等映射(Identity Mapping),即让线性地址与物理地址相等。这样可以确保在开启分页后,当前正在执行的代码仍然可以正常访问。待分页稳定运行后,再切换到完整的虚拟地址映射。

3.6 总结

CR0 寄存器的 PE 位和 PG 位是 x86 分页机制的“总开关”。PE 位开启保护模式,为分页提供段式内存管理基础;PG 位开启分页,实现线性地址到物理地址的转换。开启分页必须遵循严格的顺序:先初始化 GDT 并设置 PE 位,再初始化页表并设置 CR3,最后设置 PG 位。任何步骤的缺失或顺序错误都会导致处理器异常。理解这一流程是后续学习 4 级页表结构和地址转换的基础。