4. ARM64 页表结构:ARMv8-A 的页表层级与颗粒度

ARMv8-A 架构采用 多级页表 实现虚拟地址到物理地址的转换。页表的层级数、每级页表的大小以及最终的页面颗粒度,均由系统寄存器 TCR_EL1(Translation Control Register for EL1)精确控制。理解这一结构是优化 TLB 性能和内存访问延迟的基础。

4.1 页表层级与 TCR_EL1 配置

ARMv8-A 支持 3 级或 4 级页表(Stage 1 转换),具体取决于虚拟地址宽度和页面颗粒度。Linux 内核在 arm64 平台上通常使用 4 级页表(PGD → PUD → PMD → PTE),以支持 48 位虚拟地址空间。

TCR_EL1 寄存器中的关键字段决定了页表结构:

字段 位域 作用 典型值(Linux)
T0SZ [5:0] 虚拟地址空间大小(64 - T0SZ 位) 16(48 位地址)
TG0 [15:14] 颗粒度(页面大小) 00(4KB)
SH0 [13:12] 内部共享属性 11(Inner Shareable)
ORGN0 [11:10] Outer 缓存策略 01(Write-Back)
IRGN0 [9:8] Inner 缓存策略 01(Write-Back)
IPS [34:32] 物理地址空间大小 根据硬件(如 40 位)
内核配置提示: 在 Linux 内核源码 arch/arm64/include/asm/pgtable-hwdef.h 中,TCR_T0SZ_48 定义为 16,TCR_TG0_4K 定义为 0。这些宏在 __cpu_setup 汇编代码中被写入 TCR_EL1。

4.2 颗粒度:4K / 16K / 64K

ARMv8-A 支持三种页面颗粒度,每种颗粒度对应不同的页表层级结构和地址拆分方式。颗粒度越大,单张页表覆盖的地址范围越大,但内部碎片也可能增加。

颗粒度 页表层级数 每级页表条目数 最大虚拟地址 TLB 覆盖范围
4KB 4 级(PGD→PUD→PMD→PTE) 512 条目/级 48 位 较小,但灵活
16KB 3 级(PGD→PMD→PTE) 2048 条目/级 48 位 中等
64KB 3 级(PGD→PMD→PTE) 8192 条目/级 48 位 最大,适合大内存

Linux 内核默认使用 4KB 颗粒度,因为它与 x86 兼容性最好,且内存碎片控制更精细。但在某些高性能计算或大内存场景下,可以选择 64KB 颗粒度以减少 TLB 缺失。

颗粒度选择权衡:
  • 4KB:页表层级多(4 级),TLB 覆盖范围小,但内存浪费少,适合通用场景。
  • 64KB:页表层级少(3 级),单次 TLB 填充可覆盖更大连续内存,但内部碎片风险高,适合大页数据库或 HPC。

4.3 4KB 颗粒度下的地址拆分

以 4KB 颗粒度、48 位虚拟地址为例,虚拟地址被拆分为 9 位索引 + 9 位索引 + 9 位索引 + 9 位索引 + 12 位页内偏移。每一级索引对应一级页表查找。

虚拟地址(48 位):
| 63 | 62 ... 48 | 47 ... 39 | 38 ... 30 | 29 ... 21 | 20 ... 12 | 11 ... 0 |
|----|-----------|-----------|-----------|-----------|-----------|----------|
| 符号扩展 | 未使用    | PGD 索引  | PUD 索引  | PMD 索引  | PTE 索引  | 页内偏移 |
| (16 位)  | (1 位)    | (9 位)    | (9 位)    | (9 位)    | (9 位)    | (12 位)  |

每一级页表包含 512 个条目(2^9),每个条目 8 字节,因此每级页表大小为 4KB,恰好与页面大小一致,便于分配和管理。

4.4 ARM64 页表结构 SVG 图

下图展示了 ARMv8-A 在 4KB 颗粒度下的 4 级页表转换流程:

ARM64 4 级页表转换流程(4KB 颗粒度) 虚拟地址(48 位):PGD 索引(9) | PUD 索引(9) | PMD 索引(9) | PTE 索引(9) | 页内偏移(12) PGD 页全局目录 PUD 页上级目录 PMD 页中间目录 PTE 页表条目 物理页 4KB 数据 说明 每级页表包含 512 个条目(9 位索引),每个条目 8 字节,页表大小 = 4KB。 MMU 依次读取 PGD → PUD → PMD → PTE,最终得到物理页基址 + 页内偏移。

4.5 16KB 与 64KB 颗粒度的地址拆分

当颗粒度增大时,页内偏移位数增加,页表索引位数减少,从而减少页表层级:

颗粒度 页内偏移 每级索引位数 页表层级 每级条目数
4KB 12 位 9 位 4 级 512
16KB 14 位 11 位 3 级 2048
64KB 16 位 13 位 3 级 8192

以 64KB 颗粒度为例,虚拟地址拆分为:13 位 PGD 索引 + 13 位 PMD 索引 + 13 位 PTE 索引 + 16 位页内偏移。每级页表包含 8192 个条目,页表大小为 64KB(8192 × 8 字节)。

注意事项: 64KB 颗粒度下,每级页表大小等于一个页面(64KB),这可能导致页表分配时内存占用较大。此外,某些 DMA 设备可能不支持 64KB 颗粒度,需要内核进行 bounce buffer 处理。

4.6 页表条目格式

每个页表条目(PTE)在 ARM64 中为 8 字节,包含物理地址高位、访问权限、内存属性、脏位、有效位等。以下是一个典型的 PTE 格式:

位域分布(64 位 PTE):
| 63:48 | 47:12 | 11:10 | 9 | 8 | 7:6 | 5 | 4 | 3 | 2 | 1 | 0 |
|-------|-------|-------|---|---|-----|---|---|---|---|---|---|
| 物理地址高位 | 物理地址低位 | 属性 | AF | 脏位 | 权限 | 安全 | 非全局 | 可执行 | 用户 | 只读 | 有效 |
  • 位 [0]:有效位(Valid),0 表示该条目无效,MMU 触发缺页异常。
  • 位 [1]:只读位(Read-Only),配合权限位控制写访问。
  • 位 [2]:用户位(User),允许用户态访问。
  • 位 [10:8]:内存属性(AttrIndx),指向 MAIR_EL1 中的属性配置。
  • 位 [11]:非全局位(nG),控制 TLB 是否全局刷新。

4.7 页表配置与性能优化

合理配置 TCR_EL1 和选择颗粒度,可以显著影响 TLB 命中率和页表遍历开销:

  • 减少页表层级:使用 64KB 颗粒度将 4 级页表降为 3 级,减少每次地址转换的内存访问次数。
  • 利用大页(Huge Pages):在 PMD 或 PUD 层级设置块映射(Block Mapping),跳过后续页表查找,TLB 单次覆盖 2MB 或 1GB 连续内存。
  • TLB 一致性:修改页表后,必须执行 TLBI 指令(如 tlbi vmalle1)使失效 TLB,否则可能访问到旧映射。
内核配置示例: 在 arch/arm64/configs/defconfig 中,可以通过 CONFIG_ARM64_4K_PAGES=y 或 CONFIG_ARM64_64K_PAGES=y 选择颗粒度。重新编译内核后生效。