3. 页表基础概念:页表层级、页大小、虚拟地址与物理地址

在 ARM64 架构中,MMU(内存管理单元)通过页表完成虚拟地址到物理地址的转换。页表是一个多级查找结构,其设计直接影响系统性能、内存开销和启动稳定性。本章深入剖析页表的层级结构、支持的页大小以及地址转换的核心机制,为后续 head.S 中的页表构建与故障排查奠定基础。

3.1 页表层级结构(L0/L1/L2/L3)

ARM64 使用最多 4 级页表:L0、L1、L2、L3。每一级页表包含若干页表项(PTE),每个 PTE 指向下一级页表的基地址或直接映射为一个大页(块)。页表级数由虚拟地址宽度和页大小共同决定。

关键概念: 页表级数并非固定为 4 级。当使用 4KB 页且地址宽度为 48 位时,启用完整的 L0→L1→L2→L3 四级结构;若使用 64KB 页,则只需 L0→L1→L2 三级。
ARM64 四级页表转换结构(4KB 页,48 位虚拟地址) L0 页表 9 位索引 (47:39) L1 页表 9 位索引 (38:30) L2 页表 9 位索引 (29:21) L3 页表 9 位索引 (20:12) 物理地址 说明: • 虚拟地址 [47:39] 索引 L0 表项,找到 L1 页表基址。 • 虚拟地址 [38:30] 索引 L1 表项,找到 L2 页表基址。 • 虚拟地址 [29:21] 索引 L2 表项,找到 L3 页表基址。 • 虚拟地址 [20:12] 索引 L3 表项,得到物理页帧基址 + 页内偏移 [11:0]。

3.2 页大小与页表级数的关系

ARM64 支持三种标准页大小:4KB、16KB、64KB。页大小决定了虚拟地址中页内偏移的位数,进而影响页表级数和每级索引的位数。

页大小 页内偏移位数 虚拟地址宽度 页表级数 每级索引位数
4KB 12 位 [11:0] 48 位 4 级 (L0-L3) 9 位 × 4
16KB 14 位 [13:0] 48 位 4 级 (L0-L3) 11 位 × 3 + 9 位
64KB 16 位 [15:0] 48 位 3 级 (L0-L2) 13 位 × 2 + 12 位
启动阶段建议: 在 head.S 早期代码中,通常使用 4KB 页 + 4 级页表,因为这是最通用、兼容性最好的配置。如果后续需要减少 TLB 缺失或降低页表内存开销,可考虑切换到 64KB 页。

3.3 虚拟地址与物理地址的转换过程

ARM64 的地址转换核心是 多级页表查找。CPU 将虚拟地址分解为多个索引字段,依次遍历各级页表,最终得到物理页帧号(PFN),再与页内偏移拼接形成完整的物理地址。

虚拟地址分解与物理地址合成(4KB 页,48 位地址) 虚拟地址 (VA) [47:0] 47:39 (L0索引) | 38:30 (L1索引) | 29:21 (L2索引) | 20:12 (L3索引) | 11:0 (页内偏移) 多级页表查找(L0 → L1 → L2 → L3) 物理地址 (PA) = 页帧号 (PFN) + 页内偏移 [11:0]

3.4 页表项(PTE)的关键字段

每个页表项(PTE)是一个 64 位描述符,包含物理地址、访问权限、内存属性等关键信息。理解 PTE 的布局对于调试页表错误至关重要。

位域 宽度 描述
[63:48] 16 位 物理地址高位(扩展物理地址,取决于实现)
[47:12] 36 位 物理页帧号(PFN),与页内偏移拼接形成物理地址
[11:10] 2 位 访问权限(AP)
[9:8] 2 位 内存属性索引(AttrIndx)
[7] 1 位 非安全(NS)
[6] 1 位 访问标志(AF)
[5] 1 位 共享性(SH)
[4:2] 3 位 内存类型(MT)
[1] 1 位 可写(W)
[0] 1 位 有效位(V):1=有效,0=无效
常见故障点: 在 head.S 中构建页表时,最容易遗漏的是有效位(bit 0)未置 1,或物理地址高位(bits [63:48])未正确填充。这会导致 MMU 使能后立即触发同步异常(Data Abort 或 Translation Fault)。

3.5 页表对齐与内存布局要求

ARM64 要求页表基地址必须按页大小对齐。例如,4KB 页的页表基地址必须 4KB 对齐(低 12 位为 0)。此外,各级页表在内存中的布局必须连续,且不能跨越页边界。

  • L0 页表: 必须 4KB 对齐,包含 512 个 PTE(4KB 页时)。
  • L1 页表: 必须 4KB 对齐,包含 512 个 PTE。
  • L2 页表: 必须 4KB 对齐,包含 512 个 PTE。
  • L3 页表: 必须 4KB 对齐,包含 512 个 PTE。
启动代码实践: 在 head.S 中,通常使用 .align 12 或 .balign 4096 指令确保页表符号对齐。例如:
.balign 4096
L0_page_table:
  .space 4096   // 512 个 PTE,每个 8 字节

3.6 页表构建的典型流程

在 ARM64 启动早期,head.S 构建页表通常遵循以下步骤:

  1. 清零页表区域: 使用 dc zva 或 stp xzr, xzr, [x0], #16 循环清零。
  2. 填充 L0 表项: 指向 L1 页表基地址,设置有效位和访问权限。
  3. 填充 L1 表项: 指向 L2 页表基地址,或直接映射为 2MB 块(如果使用块映射)。
  4. 填充 L2 表项: 指向 L3 页表基地址,或直接映射为 1GB 块。
  5. 填充 L3 表项: 映射 4KB 页,设置物理地址、权限和内存属性。
  6. 设置 TTBR0_EL1: 将 L0 页表基地址写入 TTBR0_EL1。
  7. 使能 MMU: 设置 SCTLR_EL1.M 位,执行 ISB 和 DSB 同步。
调试技巧: 如果 MMU 使能后系统崩溃,优先检查 TTBR0_EL1 的值是否与 L0 页表符号地址一致,以及 L0 页表第一个 PTE 的有效位是否为 1。使用 JTAG 或模拟器读取这些寄存器可以快速定位问题。