4. 4级页表结构总览:PML4、PDPT、PD、PT的层级关系与索引方式

在 x86_64 长模式下,Linux 内核使用 4 级分页 机制将 48 位线性地址转换为 52 位物理地址。这 4 级页表分别为:PML4(Page Map Level 4)、PDPT(Page Directory Pointer Table)、PD(Page Directory)和 PT(Page Table)。每一级负责从线性地址中提取对应的索引,逐级查找,最终定位到物理页框。

核心要点: 4 级页表本质上是一个 多级索引数组。线性地址被切分为 5 个部分:4 个索引(每级 9 位)和 1 个页内偏移(12 位)。CPU 通过 CR3 寄存器找到 PML4 表基址,然后逐级索引,最终获得物理地址。

4.1 线性地址的划分

一个 48 位线性地址被划分为 5 个字段,从高位到低位依次为:

字段 位范围 位数 作用
PML4 索引 47:39 9 在 PML4 表中索引一个条目(PML4E)
PDPT 索引 38:30 9 在 PDPT 表中索引一个条目(PDPTE)
PD 索引 29:21 9 在 PD 表中索引一个条目(PDE)
PT 索引 20:12 9 在 PT 表中索引一个条目(PTE)
页内偏移 11:0 12 在物理页框内的字节偏移

每个索引占用 9 位,因此每级页表最多包含 512 个条目(2^9)。每个条目占用 8 字节(64 位),因此每级页表的大小为 4 KB(512 × 8),恰好与一个物理页框大小相同。

4.2 4级页表层级关系与索引流程

下图展示了从线性地址到物理地址的完整转换路径:

4级页表地址转换流程 PML4索引 (9位) PDPT索引 (9位) PD索引 (9位) PT索引 (9位) 页内偏移 (12位) CR3 寄存器 PML4 表 基址来自 CR3 PML4索引 PDPT 表 基址来自 PML4E PDPT索引 PD 表 基址来自 PDPTE PD索引 PT 表 基址来自 PDE PT索引 物理页框 基址来自 PTE 页内偏移 物理地址 页框基址 + 偏移 说明: 1. 每级页表包含 512 个条目,每个条目 8 字节,共 4 KB。 2. 如果某级条目的 Present 位为 0,则触发缺页异常(Page Fault)。

4.3 索引方式详解

地址转换的每一步都遵循相同的模式:

  1. 获取表基址: 从 CR3 寄存器(PML4 基址)或上一级页表条目中读取下一级页表的物理基址。
  2. 提取索引: 从线性地址中提取对应级别的 9 位索引。
  3. 计算条目地址: 条目地址 = 表基址 + 索引 × 8。
  4. 读取条目: 从内存中读取 8 字节的页表条目。
  5. 检查权限: 验证 Present、R/W、U/S 等标志位。
  6. 获取下一级基址: 从条目中提取物理地址的高 40 位(页框号),作为下一级页表的基址。
关键公式: 对于线性地址 LA,其 PML4 索引为 (LA >> 39) & 0x1FF,PDPT 索引为 (LA >> 30) & 0x1FF,PD 索引为 (LA >> 21) & 0x1FF,PT 索引为 (LA >> 12) & 0x1FF,页内偏移为 LA & 0xFFF。

4.4 页表条目结构

每个页表条目(PML4E、PDPTE、PDE、PTE)都是 64 位,其通用结构如下:

位范围 字段 说明
63:52 保留 / NX 第 63 位为 NX(不可执行)位(如果启用 NX 支持)
51:12 物理页框号 下一级页表或物理页的基址(高 40 位)
11:9 AVL 软件可用位(操作系统可自定义)
8 G 全局页(Global)
7 PS 页大小(Page Size),仅在 PDE 和 PDPTE 中有效
6 D 脏位(Dirty),仅在 PDE 和 PTE 中有效
5 A 访问位(Accessed)
4 PCD 页级缓存禁用(Page-level Cache Disable)
3 PWT 页级写透(Page-level Write-Through)
2 U/S 用户/超级用户(User/Supervisor)
1 R/W 读/写(Read/Write)
0 P 存在位(Present)
注意: 在 PML4 和 PDPT 级别,PS 位(位 7)用于指示是否使用 1 GB 大页(PDPTE)或 2 MB 大页(PDE)。如果 PS 位被置位,则地址转换在该级终止,不再继续查找下一级页表。

4.5 4级页表的内存占用

每个进程拥有独立的页表层次结构。下表展示了 4 级页表在不同情况下的内存占用:

页表级别 条目数 每个条目大小 表大小 覆盖的虚拟地址范围
PML4 512 8 字节 4 KB 整个 48 位地址空间(256 TB)
PDPT 512 8 字节 4 KB 512 GB(每个 PML4E)
PD 512 8 字节 4 KB 1 GB(每个 PDPTE)
PT 512 8 字节 4 KB 2 MB(每个 PDE)

对于典型的 4 KB 页,一个完整的 4 级页表层次结构理论上需要 4 个页框(PML4、PDPT、PD、PT 各一个),共 16 KB。然而,由于大多数进程只使用地址空间的一小部分,内核会按需分配页表,实际内存占用远小于理论最大值。

4.6 实战:手动计算地址转换

假设线性地址为 0x7F3A_5B1C_8D20,我们手动计算其各级索引:

线性地址: 0x7F3A5B1C8D20
二进制:    0111 1111 0011 1010 0101 1011 0001 1100 1000 1101 0010 0000

PML4索引:  位 [47:39] = 0xFE (254)
PDPT索引:  位 [38:30] = 0x74 (116)
PD索引:    位 [29:21] = 0x2D (45)
PT索引:    位 [20:12] = 0x1C (28)
页内偏移:  位 [11:0]  = 0xD20 (3360)

转换过程如下:

  1. 从 CR3 获取 PML4 表基址。
  2. 在 PML4 表中索引第 254 个条目,读取 PML4E,获取 PDPT 表基址。
  3. 在 PDPT 表中索引第 116 个条目,读取 PDPTE,获取 PD 表基址。
  4. 在 PD 表中索引第 45 个条目,读取 PDE,获取 PT 表基址。
  5. 在 PT 表中索引第 28 个条目,读取 PTE,获取物理页框基址。
  6. 物理地址 = 物理页框基址 + 0xD20。
实战建议: 在 Linux 内核中,可以使用 pgd_offset()、pud_offset()、pmd_offset() 和 pte_offset() 宏来遍历当前进程的页表。这些宏封装了索引计算和条目读取的逻辑,是理解 4 级页表的最佳入口。