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 位) |
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 级页表转换流程:
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 字节)。
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 选择颗粒度。重新编译内核后生效。