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 位物理地址的完整流程:
4.4 地址转换步骤详解
以虚拟地址 0x7F3A_8B4C_2D1E 为例,转换过程分为以下步骤:
- 提取索引: 将虚拟地址分解为五个部分:
PML4 索引: 位 47:39 → 0xFE PDPT 索引: 位 38:30 → 0x3A PD 索引: 位 29:21 → 0x8B PT 索引: 位 20:12 → 0x4C 偏移: 位 11:0 → 0x2D1E - 读取 CR3: 获取当前进程的 PML4 表物理基址(例如
0x100000)。 - 查找 PML4 项: 在 PML4 表基址 +
0xFE × 8处读取 8 字节页表项,得到 PDPT 表物理基址。 - 查找 PDPT 项: 在 PDPT 表基址 +
0x3A × 8处读取,得到 PD 表物理基址。 - 查找 PD 项: 在 PD 表基址 +
0x8B × 8处读取,得到 PT 表物理基址。 - 查找 PT 项: 在 PT 表基址 +
0x4C × 8处读取,得到物理页基址(例如0x7C0000)。 - 组合物理地址: 物理页基址
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 在地址转换中的位置:
4.8 总结
x86_64 四级页表通过 PML4、PDPT、PD、PT 四级结构,将 48 位虚拟地址高效转换为 52 位物理地址。CR3 寄存器作为页表遍历的起点,TLB 作为加速缓存,共同构成了现代 CPU 内存管理单元(MMU)的核心。理解这一机制是掌握 Linux 内核内存管理、进程地址空间隔离和性能优化的基础。