3. ARMv8-A 页表层级结构
ARMv8-A 架构采用多级页表(Multi-level Page Table)将虚拟地址(VA)转换为物理地址(PA)。页表的层级数及每级索引的位数,由页面大小(Page Size)和虚拟地址宽度共同决定。本章深入剖析 4 级页表(L0/L1/L2/L3)的硬件遍历过程、页表项(PTE)格式以及不同页面大小的配置差异。
3.1 4 级页表结构(L0 → L1 → L2 → L3)
当使用 4KB 页面且虚拟地址宽度为 48 位时,ARMv8-A 采用标准的 4 级页表。虚拟地址被划分为 5 个部分:L0 索引、L1 索引、L2 索引、L3 索引和页内偏移(Page Offset)。
下图展示了 4 级页表的硬件遍历流程:
TTBR0_EL1(用户空间)或 TTBR1_EL1(内核空间)获取 L0 页表基地址。每级页表项指向下一级页表的物理基地址,直到 L3 页表项指向最终的 4KB 物理页。
3.2 页表项(PTE)格式
每个页表项(Page Table Entry, PTE)占用 8 字节(64 位)。其格式根据页表层级不同略有差异,但核心字段保持一致。下表列出了 L0/L1/L2 描述符(指向下一级页表)和 L3 描述符(指向物理页)的通用格式:
| 位域 | 宽度(位) | 描述 |
|---|---|---|
| [63:48] | 16 | 物理地址高位(PA[47:32]),用于扩展物理地址 |
| [47:12] | 36 | 物理地址低位(PA[31:0]),4KB 对齐 |
| [11:10] | 2 | 访问权限(AP)位:控制读/写权限 |
| [9] | 1 | 非安全(NS)位:1=非安全空间 |
| [8] | 1 | 访问标志(AF):1=已访问,0=未访问(触发缺页异常) |
| [7:6] | 2 | 共享属性(SH):00=非共享,01=不可预测,10=外部共享,11=内部共享 |
| [5:4] | 2 | 内存属性(AttrIndx):指向 MAIR_EL1 中的属性索引 |
| [3] | 1 | 非叶(nG):1=全局映射,0=非全局(进程特有) |
| [2] | 1 | 可执行(UXN/PXN):0=可执行,1=不可执行 |
| [1] | 1 | 可写(Rd/Wr):0=只读,1=读写 |
| [0] | 1 | 有效位(Valid):0=无效,1=有效 |
3.3 页面大小与层级关系
ARMv8-A 支持三种页面大小:4KB、16KB 和 64KB。页面大小直接影响页表层级数、每级索引位数以及块映射(Block Mapping)的大小。下表总结了不同配置下的关键参数:
| 页面大小 | 虚拟地址宽度 | 页表层级数 | L0索引位数 | L1索引位数 | L2索引位数 | L3索引位数 | 页内偏移位数 |
|---|---|---|---|---|---|---|---|
| 4KB | 48 位 | 4 级 | 9 位 | 9 位 | 9 位 | 9 位 | 12 位 |
| 16KB | 48 位 | 3 级 | — | 13 位 | 13 位 | 13 位 | 14 位 |
| 64KB | 48 位 | 2 级 | — | — | 14 位 | 14 位 | 16 位 |
从上表可以看出:
- 4KB 页面:使用完整的 4 级页表,每级索引 9 位,共 512 个表项。页内偏移 12 位,覆盖 4KB 范围。
- 16KB 页面:减少为 3 级页表(L1/L2/L3),每级索引 13 位,共 8192 个表项。页内偏移 14 位。
- 64KB 页面:仅需 2 级页表(L2/L3),每级索引 14 位,共 16384 个表项。页内偏移 16 位。
3.4 块映射(Block Mapping)
ARMv8-A 允许在 L0、L1 或 L2 级使用块映射(Block Mapping),将一大段连续虚拟地址直接映射到物理地址,跳过后续的页表层级。块映射的大小取决于当前层级:
- L0 块映射:映射 512GB 区域(仅 4KB 页面配置)
- L1 块映射:映射 1GB 区域(4KB 页面)或 2MB 区域(16KB 页面)
- L2 块映射:映射 2MB 区域(4KB 页面)或 32MB 区域(16KB 页面)
块映射通过将页表项中的位 [1] 设置为 1 来标识。块映射描述符的格式与页表描述符类似,但物理地址字段覆盖更大的连续范围。
3.5 页表基址寄存器(TTBR0_EL1 / TTBR1_EL1)
ARMv8-A 提供两个页表基址寄存器:
- TTBR0_EL1:用于用户空间(EL0)的页表基址。通常每个进程拥有独立的用户页表。
- TTBR1_EL1:用于内核空间(EL1)的页表基址。所有进程共享同一个内核页表。
虚拟地址的最高位(VA[47])决定使用哪个寄存器:
- VA[47] = 0:使用
TTBR0_EL1(用户空间,地址范围 0x0000_0000_0000_0000 ~ 0x0000_7FFF_FFFF_FFFF) - VA[47] = 1:使用
TTBR1_EL1(内核空间,地址范围 0xFFFF_8000_0000_0000 ~ 0xFFFF_FFFF_FFFF_FFFF)
TTBR0_EL1 为新进程的用户页表基址,并执行 TLBI 指令使失效旧的 TLB 条目。内核页表(TTBR1_EL1)保持不变。
3.6 页表遍历与 TLB 缓存
MMU 在每次地址转换时,首先检查 TLB(Translation Lookaside Buffer)中是否缓存了对应的页表条目。如果 TLB 命中,则直接返回物理地址;如果 TLB 未命中,则启动页表遍历(Page Table Walk):
- 从
TTBR0_EL1或TTBR1_EL1获取 L0 页表基地址。 - 使用 VA[47:39] 作为索引,从 L0 页表中读取 L0 描述符。
- 如果 L0 描述符是块映射,则直接计算物理地址;否则,从描述符中提取 L1 页表基地址。
- 使用 VA[38:30] 作为索引,从 L1 页表中读取 L1 描述符。
- 重复步骤 3-4,直到读取 L3 描述符(或遇到块映射)。
- 从 L3 描述符中提取物理页基地址,加上 VA[11:0] 得到最终物理地址。
- 将转换结果缓存到 TLB 中,供后续访问使用。