4. 页表与页表项结构
页表是 MMU 实现虚拟地址到物理地址转换的核心数据结构。ARMv7-A 架构支持一级页表(L1)和二级页表(L2)两级查询机制,通过页表描述符(Page Table Descriptor)中的属性位控制内存访问权限、缓存策略和执行特性。本节将详细拆解两级页表的组织方式、描述符格式以及关键属性位的含义。
4.1 一级页表(L1 Page Table)
一级页表也称为“页表目录”(Page Table Directory),其基地址存储在协处理器 CP15 的 TTBR0/TTBR1 寄存器中。一级页表包含 4096 个表项,每个表项 32 位(4 字节),覆盖 4GB 虚拟地址空间。每个一级表项映射 1MB 的虚拟地址块。
4.1.1 一级页表描述符类型
一级页表描述符的低 2 位(bits[1:0])用于区分描述符类型:
| bits[1:0] | 描述符类型 | 说明 |
|---|---|---|
| 0b00 | 无效(Fault) | 该 1MB 区域未映射,访问将触发缺页异常 |
| 0b01 | 页表项(Page Table) | 指向二级页表的基地址,用于 4KB 细粒度映射 |
| 0b10 | 段描述符(Section) | 直接映射 1MB 物理内存块,无需二级页表 |
| 0b11 | 超段描述符(Supersection) | 映射 16MB 物理内存块,用于大块连续内存 |
4.1.2 段描述符(Section Descriptor)格式
当一级描述符类型为段映射(0b10)时,其 32 位格式如下:
位域分布(31 → 0):
[31:20] 物理基地址[31:12] — 物理地址高 20 位,对应 1MB 对齐
[19:12] 域(Domain) — 访问控制域标识符
[11:10] 不可用(SBZ) — 应写 0
[9] nG(非全局) — 0=全局,1=进程私有
[8] S(可共享) — 多核一致性控制
[7:6] AP[2:1] — 访问权限位
[5] TEX[2] — 内存类型扩展位
[4] AP[0] — 访问权限位
[3] C(Cacheable) — 可缓存
[2] B(Bufferable) — 可缓冲
[1:0] 0b10 — 段描述符标识
4.2 二级页表(L2 Page Table)
当一级描述符类型为页表项(0b01)时,MMU 会跳转到二级页表进行更细粒度的地址转换。二级页表包含 256 个表项,每个表项 32 位(4 字节),覆盖一级表项对应的 1MB 虚拟地址空间。每个二级表项映射 4KB 的物理页。
4.2.1 二级页表描述符类型
二级页表描述符的低 2 位(bits[1:0])同样用于区分类型:
| bits[1:0] | 描述符类型 | 说明 |
|---|---|---|
| 0b00 | 无效(Fault) | 该 4KB 页未映射 |
| 0b01 | 大页描述符(Large Page) | 映射 64KB 物理块(已弃用,兼容 ARMv5) |
| 0b10 | 小页描述符(Small Page) | 映射 4KB 物理页,最常用 |
| 0b11 | 极小页描述符(Tiny Page) | 映射 1KB 物理块(已弃用) |
4.2.2 小页描述符(Small Page Descriptor)格式
二级小页描述符(0b10)的 32 位格式:
位域分布(31 → 0):
[31:12] 物理基地址[31:12] — 物理地址高 20 位,对应 4KB 对齐
[11:9] TEX[2:0] — 内存类型扩展位
[8] S(可共享) — 多核一致性控制
[7:6] AP[2:1] — 访问权限位
[5] nG(非全局) — 0=全局,1=进程私有
[4] AP[0] — 访问权限位
[3] C(Cacheable) — 可缓存
[2] B(Bufferable) — 可缓冲
[1:0] 0b10 — 小页描述符标识
4.3 两级页表查询流程
以下 SVG 图展示了 ARM MMU 从虚拟地址到物理地址的两级页表查询完整流程:
4.4 页表属性位详解
页表描述符中的属性位决定了 MMU 如何对待该内存区域。以下表格汇总了关键属性位的功能:
| 属性位 | 位置 | 功能描述 | 典型值 |
|---|---|---|---|
| AP[2:0] | L1[7:6,4] / L2[7:6,4] | 访问权限控制:特权/用户读/写 | 0b111=全读写,0b101=只读 |
| Domain | L1[19:12] | 域标识(0~15),配合 DACR 寄存器实现域级保护 | 0=管理域,3=客户域 |
| C / B | L1[3:2] / L2[3:2] | Cacheable 和 Bufferable 策略 | C=1,B=1 写回;C=1,B=0 写通 |
| TEX[2:0] | L2[11:9] | 内存类型扩展,与 C/B 共同决定内存属性 | 0b000=普通内存,0b001=设备内存 |
| nG | L1[9] / L2[5] | 非全局位:0=全局映射(内核),1=进程私有 | 内核空间=0,用户空间=1 |
| S | L1[8] / L2[8] | 可共享位:多核一致性维护 | 1=共享内存区域 |
| XN | L1[0](段) | 执行禁止位:阻止该区域执行代码 | 1=不可执行(数据页) |
4.4.1 访问权限(AP)位详解
AP 位组合决定了不同特权级下的内存访问能力:
| AP[2:0] | 特权模式 | 用户模式 | 典型用途 |
|---|---|---|---|
| 0b000 | 无访问 | 无访问 | 保留/故障注入 |
| 0b001 | 读/写 | 无访问 | 内核私有数据 |
| 0b010 | 读/写 | 只读 | 只读系统调用表 |
| 0b011 | 读/写 | 读/写 | 用户进程数据段 |
| 0b101 | 只读 | 无访问 | 内核代码段 |
| 0b111 | 只读 | 只读 | 用户代码段 |
4.5 页表配置示例:内核空间段映射
以下代码展示如何在 Linux 内核启动早期设置一级页表段映射,将物理地址 0x80000000~0x80FFFFFF 映射到虚拟地址 0xC0000000~0xC0FFFFFF:
/* 汇编伪代码:设置一级页表段映射 */
/* TTBR0 已指向 16KB 对齐的一级页表基地址 L1_TABLE */
/* 计算一级页表索引:VA[31:20] = 0xC00 >> 20 = 0x300 */
/* 页表项偏移 = 0x300 * 4 = 0xC00 */
LDR r0, =L1_TABLE /* 一级页表基地址 */
LDR r1, =0xC0000000 /* 虚拟地址 */
LSR r1, r1, #20 /* 右移20位得到索引 */
LSL r1, r1, #2 /* 乘以4得到字节偏移 */
ADD r0, r0, r1 /* 指向页表项地址 */
/* 构造段描述符 */
/* 物理基址 0x80000000 >> 20 = 0x800 */
/* 域=0,AP=全读写,C=1,B=1(写回缓存),类型=段 */
LDR r2, =0x80000000 /* 物理基地址 */
LDR r3, =0x00000C0E /* 属性:域0 | AP全读写 | C=1 | B=1 | 段标识0b10 */
ORR r2, r2, r3 /* 合并基地址和属性 */
STR r2, [r0] /* 写入一级页表项 */
/* 使能 MMU 前需执行 DSB 和 ISB 同步指令 */
DSB
ISB
4.6 页表属性位与内存类型映射
ARM 架构通过 TEX、C、B 三位的组合定义内存类型,直接影响 CPU 的缓存行为和访问排序:
| TEX[2:0] | C | B | 内存类型 | 典型场景 |
|---|---|---|---|---|
| 0b000 | 0 | 0 | 强序(Strongly Ordered) | CPU 间同步变量 |
| 0b000 | 0 | 1 | 设备内存(Device) | MMIO 寄存器 |
| 0b000 | 1 | 0 | 正常内存写通(Write-Through) | 帧缓冲区 |
| 0b000 | 1 | 1 | 正常内存写回(Write-Back) | 普通 RAM |
| 0b001 | 0 | 0 | 不可缓存正常内存 | DMA 缓冲区 |
| 0b001 | 1 | 1 | 写回写分配(Write-Back Write-Allocate) | 高性能堆内存 |
4.7 页表配置的调试与验证
在嵌入式 Linux 驱动开发中,可以通过以下方法验证页表配置是否正确:
- 读取 TTBR0/TTBR1: 通过 MRC 指令读取 CP15 的 c2 寄存器,获取一级页表物理基地址。
- 遍历页表项: 在内核模块中通过
phys_to_virt()映射页表物理地址,逐项检查描述符类型和属性位。 - 使用 /proc/xxx/page_tables: 某些内核调试补丁会导出页表信息到 procfs。
- JTAG 调试器: 直接读取物理内存中的页表项,比对预期值。
以下 SVG 图展示页表项属性位的位级布局,帮助开发者快速定位配置错误:
4.8 本章小结
- 一级页表包含 4096 个 4 字节描述符,支持段映射(1MB)和指向二级页表的页表项。
- 二级页表包含 256 个 4 字节描述符,支持小页映射(4KB),是用户空间内存管理的基础。
- 页表描述符的属性位(AP、Domain、C/B、TEX、nG、S、XN)精确控制每块内存的访问权限、缓存行为和共享属性。
- 驱动开发中必须为 MMIO 区域配置设备内存类型,避免缓存导致的外设访问异常。
- 理解页表结构是调试内存映射问题、优化 TLB 性能和实现自定义内存管理的关键前提。