3. 页表初始化流程:分配页表内存、清零、填充映射关系、设置属性
页表初始化是 MMU 使能前最关键的软件准备阶段。该阶段的核心目标是在物理内存中构建一套完整的、符合当前体系结构规范的页表结构,确保 MMU 开启后 CPU 能够按照预期进行地址翻译。整个流程可归纳为四个连续步骤:分配页表内存 → 清零 → 填充映射关系 → 设置属性。
性能要点:页表初始化阶段的效率直接影响系统启动时间。在内存受限的嵌入式系统中,页表内存的分配策略和清零方式往往是性能瓶颈所在。
3.1 分配页表内存
页表内存的分配需要满足两个核心约束:物理连续性和对齐要求。不同体系结构对页表基地址的对齐粒度有严格规定,例如 ARMv8 要求转换表基地址按 4KB 对齐,x86 的页目录基地址必须按 4KB 对齐。
| 体系结构 | 页表层级 | 对齐要求 | 典型内存占用(4GB 地址空间) |
|---|---|---|---|
| ARMv7-A(短描述符) | 2 级 | 16KB 对齐(一级页表) | 16KB + 二级页表按需分配 |
| ARMv8-A(4KB 粒度) | 3 级 | 4KB 对齐 | 4KB × 3 级 = 12KB(仅页表本身) |
| x86(32 位 PAE) | 3 级 | 32 字节对齐(PDPT) | 4KB(页目录指针表)+ 4KB(页目录)+ 4KB(页表) |
| RISC-V(Sv39) | 3 级 | 4KB 对齐 | 4KB × 3 级 = 12KB |
分配策略通常有两种:
- 静态分配:在链接脚本中预留固定大小的页表区域,适用于内存布局固定的裸机系统。优点是零运行时开销,缺点是灵活性差。
- 动态分配:在启动早期通过简单的物理内存分配器(如 bitmap 分配器)从预留区域中分配。适用于需要支持不同内存配置的通用 bootloader。
3.2 清零页表内存
清零操作确保页表中所有无效条目处于已知的安全状态。未清零的页表条目可能包含随机数据,MMU 使能后会导致不可预测的翻译错误或权限异常。
安全风险:如果页表内存未清零,残留的物理地址可能指向敏感数据区域(如安全密钥存储区),攻击者可能通过构造特定的虚拟地址访问这些区域。
清零策略对比:
| 方法 | 实现方式 | 性能(4KB 页表) | 适用场景 |
|---|---|---|---|
| 逐字节 memset | memset(page_table, 0, size) |
~2 μs(Cortex-A72 @1.5GHz) | 通用场景,代码可移植 |
| DC ZVA(Data Cache Zero by VA) | ARMv8 专用指令,按 cacheline 清零 | ~0.5 μs(相同条件) | ARMv8 优化场景 |
| DMA 批量清零 | DMA 引擎并行执行 | ~0.2 μs(不占用 CPU) | 多核 / 大型页表(>64KB) |
3.3 填充映射关系
填充映射关系是页表初始化的核心步骤,需要将虚拟地址空间中的各个区域映射到对应的物理地址。典型的映射需求包括:
- 恒等映射(Identity Mapping):将虚拟地址等于物理地址的区域映射,通常用于 MMU 使能瞬间的过渡期。
- 内核映射:将内核代码段、数据段映射到高地址虚拟空间(如 0xC0000000 以上)。
- 外设映射:将 MMIO 地址空间映射为设备内存类型(Device-nGnRE)。
- 页表自映射:将页表本身映射到虚拟地址空间,便于运行时修改。
3.4 设置属性
每个页表条目除了包含物理地址外,还需要设置访问权限和内存类型属性。这些属性决定了 CPU 对该内存区域的访问行为。
关键属性字段(以 ARMv8 为例):
| 属性 | 位域 | 可选值 | 说明 |
|---|---|---|---|
| 访问权限(AP) | [7:6] | 00 = EL0/EL1 可读写 01 = EL1 只读 10 = EL0 不可访问 |
控制异常级别访问权限 |
| 内存类型(AttrIndx) | [4:2] | 000 = Normal 001 = Device-nGnRE 010 = Strongly-Ordered |
决定缓存策略和访问顺序 |
| 可执行(XN) | [54] | 0 = 可执行 1 = 不可执行 |
防止代码注入攻击 |
| 脏位(AF) | [10] | 0 = 未访问 1 = 已访问 |
初始化为 1 避免缺页异常 |
性能优化技巧:对于大块连续内存区域(如 DDR),使用块映射(Block Mapping)代替页表映射。ARMv8 支持 2MB 和 1GB 的块映射,可显著减少页表层级遍历次数和 TLB 压力。
3.5 完整初始化流程代码示例
以下代码展示了 ARMv8-A 架构下页表初始化的核心流程,使用 4KB 粒度三级页表:
// 页表初始化流程 - ARMv8-A 示例
#define PAGE_TABLE_SIZE 0x3000 // 三级页表:L1 + L2 + L3 = 12KB
#define PAGE_TABLE_ALIGN 0x1000 // 4KB 对齐
// 步骤1:分配页表内存(静态分配)
__attribute__((aligned(PAGE_TABLE_ALIGN)))
uint64_t page_table_l1[512]; // L1 页表(1个表项覆盖 1GB)
uint64_t page_table_l2[512]; // L2 页表(1个表项覆盖 2MB)
uint64_t page_table_l3[512]; // L3 页表(1个表项覆盖 4KB)
void init_page_tables(void) {
// 步骤2:清零所有页表
memset(page_table_l1, 0, sizeof(page_table_l1));
memset(page_table_l2, 0, sizeof(page_table_l2));
memset(page_table_l3, 0, sizeof(page_table_l3));
// 步骤3:填充映射关系 - 恒等映射 0x00000000 - 0x3FFFFFFF
// L1 表项:0x00000000 - 0x3FFFFFFF(1GB 块映射)
page_table_l1[0] = (uint64_t)&page_table_l2;
page_table_l1[0] |= 0x3; // 有效 + 可访问
// L2 表项:0x00000000 - 0x001FFFFF(2MB 块映射)
for (int i = 0; i < 512; i++) {
page_table_l2[i] = (i * 0x200000); // 物理基地址
page_table_l2[i] |= 0x3; // 有效 + 可访问
}
// 步骤4:设置属性
// 设置 Normal 内存类型(MAIR 索引 0)
// 设置可读写、可执行
for (int i = 0; i < 512; i++) {
page_table_l2[i] &= ~(0x3ULL << 2); // 清除 AttrIndx
page_table_l2[i] |= (0x0ULL << 2); // AttrIndx = 0 (Normal)
page_table_l2[i] |= (0x1ULL << 6); // AP = 01 (EL1 可读写)
page_table_l2[i] |= (0x1ULL << 10); // AF = 1 (已访问)
}
// 设置 TTBR0_EL1 指向 L1 页表基地址
__asm__ volatile("msr ttbr0_el1, %0" : : "r" (page_table_l1));
__asm__ volatile("isb"); // 指令同步屏障
}
关键检查点:在 MMU 使能前,务必验证以下三点:
1. 页表基地址寄存器(TTBR0/TTBR1)已正确设置。
2. MAIR(Memory Attribute Indirection Register)已配置,确保 AttrIndx 索引有效。
3. TCR(Translation Control Register)已设置,包括页面粒度、地址空间大小和缓存策略。
1. 页表基地址寄存器(TTBR0/TTBR1)已正确设置。
2. MAIR(Memory Attribute Indirection Register)已配置,确保 AttrIndx 索引有效。
3. TCR(Translation Control Register)已设置,包括页面粒度、地址空间大小和缓存策略。
3.6 常见错误与调试方法
| 错误现象 | 可能原因 | 调试方法 |
|---|---|---|
| MMU 使能后立即触发异常 | 页表内存未清零,无效条目导致翻译错误 | 检查页表内存是否在使能前完成 memset |
| 访问外设时挂死 | 外设区域未设置为 Device 内存类型 | 检查 MAIR 配置和页表条目 AttrIndx 字段 |
| 性能异常低 | Normal 内存区域被错误设置为 Strongly-Ordered | 使用性能计数器检查缓存命中率 |
| 页表修改不生效 | TLB 未失效,缓存了旧页表条目 | 执行 TLBI(TLB Invalidate)操作 |
页表初始化是 MMU 使能前最关键的软件准备步骤。分配内存时需保证物理连续性和对齐要求;清零操作确保所有无效条目处于安全状态;填充映射关系需要覆盖所有必要的地址区域;设置属性则决定了内存的访问权限和缓存行为。这四个步骤的顺序和正确性直接影响系统的稳定性和性能。