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已经初始化完毕,页表可以迁移到主存。
ARM64启动:页表创建与MMU初始化时序鸿沟 页表创建 物理地址空间 过渡窗口 MMU关闭→开启 恒等映射关键期 MMU初始化完成 虚拟地址空间 ⚠ 鸿沟本质: 页表创建时DDR可能尚未初始化,页表只能放在SRAM中;MMU开启后,CPU立即使用虚拟地址, 要求页表所在内存区域在开启前后地址映射必须一致(恒等映射),否则CPU将立即崩溃。

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开启瞬间)
复杂度 高 (协议栈+驱动) 中 (安全+多级启动) 低 (完全控制)
UEFI / ATF / 裸机 启动流程对比 三种启动方式:页表创建与MMU初始化流程对比 UEFI ATF 裸机 SEC阶段 (无MMU) PEI阶段 (创建临时页表) DXE阶段 (开启MMU) BDS阶段 (虚拟地址) BL1 (SRAM, 无MMU) BL2 (创建页表, 恒等映射) BL31 (开启MMU, EL3) BL33 (OS, 虚拟地址) Reset向量 (无MMU) startup.S (创建页表) 开启MMU (恒等映射) C语言入口 (虚拟地址) 🔑 关键差异: UEFI在DXE阶段统一开启MMU;ATF在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原型