4. x86_64 四级页表:层次结构、CR3 寄存器与地址转换

x86_64 架构采用四级页表结构,将 48 位虚拟地址转换为 52 位物理地址。这种多级设计在支持大地址空间的同时,通过按需分配页表项有效节省内存。本章深入分析四级页表的层次结构、CR3 寄存器的作用以及完整的地址转换过程。

4.1 四级页表层次结构

x86_64 的四级页表由以下四个层级组成,每一级负责虚拟地址中不同位段的索引查找:

层级 名称 位段 页表项数 覆盖范围
PML4 Page Map Level 4 位 47:39 512 512 GB
PDPT Page Directory Pointer Table 位 38:30 512 1 GB
PD Page Directory 位 29:21 512 2 MB
PT Page Table 位 20:12 512 4 KB

每个页表项(PTE)占用 8 字节,因此每个页表大小为 4 KB(512 × 8)。虚拟地址的低 12 位作为页内偏移,用于定位 4 KB 页面内的具体字节。

关键设计点: 四级页表支持大页(Huge Page)映射。当 PDPT 项标记为 1 GB 大页时,跳过 PD 和 PT 两级;当 PD 项标记为 2 MB 大页时,跳过 PT 一级。这能显著减少 TLB 缺失并提升性能。

4.2 CR3 寄存器与页表基址

CR3 寄存器(控制寄存器 3)是 x86_64 架构中页表转换的起点。它保存当前进程的 PML4 页表物理基地址。当发生进程切换时,操作系统将新进程的 PML4 基址写入 CR3,从而立即切换整个地址空间。

  • CR3 内容: 物理地址的高 40 位(位 51:12),低 12 位用于缓存控制标志(PCD、PWT)。
  • 写入时机: 进程调度、系统调用返回、中断上下文切换时由内核写入。
  • TLB 刷新: 写入 CR3 会强制刷新整个 TLB(Translation Lookaside Buffer),确保后续地址转换使用新页表。
  • PCID 支持: 现代 x86_64 支持 PCID(Process Context IDentifiers),允许 CR3 切换时保留部分 TLB 条目,减少刷新开销。
性能提示: 频繁写入 CR3 会导致 TLB 完全刷新,造成性能损失。Linux 内核通过 TLB 批处理、大页映射和 PCID 优化来减少这种开销。

4.3 地址转换过程图解

下图展示了 x86_64 四级页表将 48 位虚拟地址转换为 52 位物理地址的完整流程:

x86_64 四级页表地址转换流程 x86_64 四级页表地址转换流程 虚拟地址 (48 位) 位 47:39 (PML4) | 位 38:30 (PDPT) | 位 29:21 (PD) | 位 20:12 (PT) | 位 11:0 (偏移) CR3 寄存器 PML4 表 512 项 × 8 字节 PDPT 表 512 项 × 8 字节 PD 表 512 项 × 8 字节 PT 表 512 项 × 8 字节 物理页 (4 KB) + 页内偏移 (位 11:0) 得到最终物理地址 每级页表使用虚拟地址中对应的位段作为索引,从 CR3 开始逐级查找,最终得到物理页基址 + 偏移

4.4 地址转换步骤详解

以虚拟地址 0x7F3A_8B4C_2D1E 为例,转换过程分为以下步骤:

  1. 提取索引: 将虚拟地址分解为五个部分:
    PML4 索引: 位 47:39 → 0xFE
    PDPT 索引: 位 38:30 → 0x3A
    PD   索引: 位 29:21 → 0x8B
    PT   索引: 位 20:12 → 0x4C
    偏移:     位 11:0  → 0x2D1E
  2. 读取 CR3: 获取当前进程的 PML4 表物理基址(例如 0x100000)。
  3. 查找 PML4 项: 在 PML4 表基址 + 0xFE × 8 处读取 8 字节页表项,得到 PDPT 表物理基址。
  4. 查找 PDPT 项: 在 PDPT 表基址 + 0x3A × 8 处读取,得到 PD 表物理基址。
  5. 查找 PD 项: 在 PD 表基址 + 0x8B × 8 处读取,得到 PT 表物理基址。
  6. 查找 PT 项: 在 PT 表基址 + 0x4C × 8 处读取,得到物理页基址(例如 0x7C0000)。
  7. 组合物理地址: 物理页基址 0x7C0000 + 偏移 0x2D1E = 0x7C2D1E。
注意事项: 如果任意一级页表项不存在(未分配),CPU 会触发页错误(Page Fault),由内核缺页异常处理程序负责分配物理页并建立映射。此外,页表项中的 Present 位(位 0)必须为 1,否则同样触发页错误。

4.5 页表项格式

每个页表项(PTE)为 64 位,其格式如下:

位段 名称 描述
位 0 Present 页面是否在物理内存中
位 1 R/W 读写权限(0=只读,1=读写)
位 2 U/S 用户/超级用户权限
位 3 PWT Write-Through 缓存策略
位 4 PCD Cache Disable 缓存禁用
位 5 Accessed 页面是否被访问过
位 6 Dirty 页面是否被写入过(仅 PT 级有效)
位 7 PS 页面大小(PDPT/PD 级指示大页)
位 8 Global 全局页面(TLB 不刷新)
位 11:9 Ignored 操作系统可用位(如用于 NUMA 信息)
位 51:12 物理地址 物理页基址或下一级页表基址
位 63:52 Reserved 保留位(必须为 0)

4.6 四级页表的内存开销

四级页表本身占用内存,其开销与进程地址空间使用量相关:

  • 最小开销: 每个进程至少需要 1 个 PML4 表(4 KB),即使未使用任何地址空间。
  • 典型开销: 对于使用 1 GB 地址空间的进程,需要 1 个 PML4 + 1 个 PDPT + 512 个 PD + 512 个 PT = 约 4 MB 页表内存。
  • 大页优化: 使用 2 MB 大页时,只需 1 个 PML4 + 1 个 PDPT + 1 个 PD = 12 KB,大幅降低页表开销。
  • 内核共享: 所有进程共享内核部分的页表(高地址区域),减少总内存占用。
Linux 内核优化: 内核使用 pgd、p4d、pud、pmd、pte 等抽象层,支持 x86_64 的四级页表以及未来可能的五级页表扩展。通过 fork() 时的写时复制(COW)机制,父子进程共享页表,仅在写入时复制,进一步节省内存。

4.7 TLB 与缓存一致性

地址转换过程中,TLB 缓存最近使用的页表项,避免每次访问都遍历四级页表:

  • TLB 命中: 直接使用缓存的物理地址,转换延迟约 1-2 个 CPU 周期。
  • TLB 缺失: 硬件遍历四级页表,延迟约 100-200 个 CPU 周期。
  • TLB 刷新: 写入 CR3、修改页表项后需要刷新 TLB,否则可能使用过时的缓存。
  • 全局页面: 设置 PTE 的 Global 位(位 8)后,即使写入 CR3 也不会刷新该条目,适用于内核代码段等常驻映射。

下图展示了 TLB 在地址转换中的位置:

TLB 在地址转换中的位置 TLB 在地址转换中的位置 CPU 虚拟地址 TLB 四级页表 遍历 物理地址 物理内存 TLB 命中 TLB 缺失

4.8 总结

x86_64 四级页表通过 PML4、PDPT、PD、PT 四级结构,将 48 位虚拟地址高效转换为 52 位物理地址。CR3 寄存器作为页表遍历的起点,TLB 作为加速缓存,共同构成了现代 CPU 内存管理单元(MMU)的核心。理解这一机制是掌握 Linux 内核内存管理、进程地址空间隔离和性能优化的基础。