1. 课程导论:为什么ARM64启动流程中页表创建与MMU初始化存在差异?不同启动方式(如UEFI、ATF、裸机)的宏观对比
1.1 核心问题:页表创建与MMU初始化的“时序鸿沟”
在ARM64系统启动过程中,页表(Page Table)的创建与MMU(Memory Management Unit)的初始化并非一个原子操作,而是两个在地址空间、执行特权级、物理资源可用性上存在显著差异的阶段。这种差异根植于ARMv8-A架构对虚拟地址(VA)与物理地址(PA)的严格分离,以及启动过程中内存控制器、DDR、页表存放位置的逐步就绪。
关键洞察: 页表是描述“虚拟→物理”映射的静态数据,而MMU是执行地址翻译的硬件状态机。在MMU开启前,CPU运行在“平坦物理地址”空间;开启后,瞬间切换到虚拟地址空间。这个切换窗口是启动流程中最脆弱的环节。
差异主要体现在以下三个维度:
- 地址空间差异: 页表创建时CPU通常运行在物理地址(或恒等映射的虚拟地址),而MMU初始化后必须立即切换到虚拟地址,要求页表本身必须位于一个“开启MMU后仍可访问”的内存区域。
- 特权级差异: 页表创建通常在EL3(安全监控模式)或EL2(虚拟化模式)完成,而MMU初始化可能发生在EL1(操作系统内核),不同异常级别的页表格式(TTBR0_EL1/EL2/EL3)和翻译机制存在细微差异。
- 资源可用性差异: 早期启动阶段(如ROM Code、BL1)可能只有片上SRAM可用,页表只能存放在有限且非缓存一致的区域;而MMU初始化时通常DDR已经初始化完毕,页表可以迁移到主存。
1.2 差异根源:启动阶段的资源约束与地址空间演变
ARM64启动流程通常经历4个阶段,每个阶段对页表创建和MMU初始化的支持程度截然不同:
| 启动阶段 | 典型固件 | 可用内存 | 页表创建能力 | MMU状态 |
|---|---|---|---|---|
| 阶段0 (ROM Code) | BootROM | 片上SRAM (几十KB) | 无(通常无页表) | 关闭 |
| 阶段1 (BL1) | ATF BL1 / U-Boot SPL | SRAM + 部分DDR (受限) | 有限(可创建一级页表) | 通常关闭,或开启恒等映射 |
| 阶段2 (BL2) | ATF BL2 / U-Boot | DDR (完整) | 完整(多级页表) | 可开启,使用虚拟地址 |
| 阶段3 (BL33/OS) | UEFI / Linux Kernel | DDR + 外设内存 | 完整(动态页表) | 必须开启,管理虚拟内存 |
实践提示: 在裸机启动中,开发者经常在BL1阶段就尝试开启MMU以加速代码执行(从低速Flash拷贝到SRAM),但此时页表必须使用恒等映射(Identity Mapping),即虚拟地址等于物理地址。这是ARM64启动中最常见的“坑”——忘记恒等映射会导致MMU开启瞬间PC指针指向非法地址。
1.3 三种启动方式的宏观对比:UEFI vs ATF vs 裸机
不同的启动方式对页表创建和MMU初始化的处理策略存在系统性差异,主要体现在页表由谁创建、在哪个异常级别创建、以及MMU开启的时机:
| 对比维度 | UEFI启动 | ATF (ARM Trusted Firmware) | 裸机 (Bare-metal) |
|---|---|---|---|
| 页表创建者 | UEFI固件 (DXE阶段) | ATF BL2 / BL31 | 开发者自建 (startup.S) |
| 页表存放位置 | DDR (UEFI分配的内存) | SRAM → DDR (逐级迁移) | 链接脚本指定 (通常DDR) |
| MMU开启时机 | DXE阶段早期 (UEFI自身需要) | BL2阶段 (为BL31准备) | 开发者控制 (通常在C语言入口前) |
| 异常级别 | EL2 (UEFI运行在HYP模式) | EL3 → EL2 → EL1 (逐级移交) | EL1 (直接运行内核) |
| 页表格式 | UEFI规范定义 (与OS兼容) | ARM标准页表 (4KB/16KB/64KB) | 开发者自定义 (通常4KB粒度) |
| 恒等映射需求 | 需要 (UEFI自身代码段) | 需要 (BL2→BL31切换) | 强烈需要 (MMU开启瞬间) |
| 复杂度 | 高 (协议栈+驱动) | 中 (安全+多级启动) | 低 (完全控制) |
1.4 差异带来的工程挑战与设计权衡
页表创建与MMU初始化的分离设计并非偶然,而是ARM64架构为了满足安全性、可移植性、性能三大目标而做出的权衡。理解这些差异有助于在实际开发中做出正确决策:
- 安全性挑战: 在ATF中,页表包含安全世界(Secure World)的内存映射信息,如果在非安全阶段(如UEFI)错误地暴露这些页表,将导致安全漏洞。因此ATF在BL2阶段创建的页表必须在进入BL33前清理或锁定。
- 可移植性挑战: 不同SoC的DDR控制器初始化序列不同,导致页表创建时DDR可能尚未就绪。裸机开发者必须编写与硬件紧密耦合的启动代码,而UEFI通过HOB(Hand-Off Block)机制将内存信息传递给下一阶段,实现了固件与OS的解耦。
- 性能挑战: 页表存放在SRAM中时,由于SRAM容量有限(通常仅几百KB),只能使用一级页表(1GB块映射),导致内存粒度粗糙;而MMU初始化后页表迁移到DDR,可以使用四级页表(4KB页),实现精细的内存管理。这种“粗→细”的页表粒度切换是性能优化的关键点。
⚠ 常见陷阱: 在裸机开发中,很多开发者试图在MMU开启后立即访问外设寄存器,却忘记为外设内存区域建立页表映射。ARM64架构下,MMU开启后所有内存访问都经过翻译,未映射的地址将触发同步异常(Synchronous Abort)。正确的做法是在开启MMU前,确保所有需要访问的内存区域(包括外设)都已建立页表项。
1.5 本章小结:差异的本质是“阶段化”与“特权级”的博弈
ARM64启动流程中页表创建与MMU初始化的差异,本质上是启动阶段化(Boot Staging)与特权级隔离(Privilege Separation)共同作用的结果。不同启动方式(UEFI/ATF/裸机)通过不同的策略来管理这种差异:
| 启动方式 | 核心策略 | 页表管理哲学 | 适用场景 |
|---|---|---|---|
| UEFI | 统一固件框架,页表由UEFI内核管理 | “一次创建,多次使用” | 服务器、桌面、标准化嵌入式 |
| ATF | 逐级移交,安全隔离 | “逐级创建,逐级清理” | 移动设备、安全关键系统 |
| 裸机 | 完全控制,最小依赖 | “按需创建,精确控制” | RTOS、深度嵌入式、FPGA原型 |