2. ARM64架构基础

在进入 ARM64 早期启动代码之前,必须先建立对 AArch64 架构基本模型的清晰认识。启动流程中的每一条指令、每一次异常向量跳转、每一个系统寄存器写入,都建立在本节所述的执行状态、异常等级、寄存器体系与内存模型之上。理解这些概念,是读懂 _start、el3_entry、el2_entry 等入口代码的前提。

一、AArch64 执行状态与 AArch32 的关系

ARMv8-A 架构引入了 AArch64 执行状态,它使用 64 位宽的通用寄存器与固定的 32 位指令编码。与之并存的 AArch32 状态则兼容早期 ARMv7 的 32 位执行模型。两者并非简单的“模式切换”,而是两套独立的执行环境:寄存器宽度、异常模型、系统寄存器命名空间都不相同。

关键点在于:执行状态由异常等级决定。同一个异常等级下,处理器可以运行在 AArch64 或 AArch32,但一旦进入更高异常等级,执行状态可能被强制切换。例如从 EL0 的 AArch32 应用触发异常进入 EL1 时,EL1 可以配置为 AArch64,此时处理器自动切换到 64 位执行状态。这种“低 EL 可为 32 位、高 EL 为 64 位”的混合模型,是 ARM64 启动代码必须处理的第一类状态问题。

AArch64 的核心特征:31 个 64 位通用寄存器 X0–X30、零寄存器 XZR/WZR、独立的 SP_ELx 栈指针、PC 不再作为通用寄存器、异常返回使用 ERET 指令而非 MOVS PC。

二、异常等级 EL0–EL3 的层级模型

ARM64 定义了四个异常等级,数值越大权限越高:

  • EL0:用户态,运行应用程序,权限最低,不能直接访问系统寄存器。
  • EL1:内核态,通常运行 Linux 内核或 RTOS,负责内存管理、中断处理。
  • EL2:虚拟化层,运行 Hypervisor,管理虚拟机与 Stage-2 地址翻译。
  • EL3:安全监控层,运行 Secure Monitor,负责安全世界与非安全世界的切换。

异常只能从低 EL 向高 EL 或同 EL 触发,不能主动降级;降级必须通过 ERET 指令配合 SPSR_ELx 与 ELR_ELx 完成。启动代码通常从 EL3 开始,依次初始化并逐级下降,最终进入 EL1 运行操作系统。

ARM64 异常等级与启动下降路径 EL3 · Secure Monitor 安全监控固件 / BL31,负责安全世界切换与平台初始化 最高权限 EL2 · Hypervisor 虚拟化层 / BL2,管理 Stage-2 翻译与虚拟机异常 虚拟化 EL1 · Kernel 操作系统内核 / BL1,处理中断、内存管理与系统调用 内核态 EL0 · User 用户应用程序,通过 SVC 等指令陷入内核 最低权限 异常上升 / 启动下降

三、通用寄存器与特殊寄存器

AArch64 提供 31 个 64 位通用寄存器 X0–X30,其低 32 位可通过 W0–W30 访问。写入 W 寄存器会自动清零对应 X 寄存器的高 32 位。此外还有零寄存器 XZR/WZR,读取恒为 0,写入被丢弃。

几个具有约定用途的寄存器在启动代码中频繁出现:

寄存器约定用途启动阶段典型场景
X0–X7参数传递 / 返回值传递设备树地址、启动参数
X8间接结果寄存器系统调用号传递
X9–X15临时寄存器启动代码临时保存中间值
X16/X17IP0/IP1,链接器暂存PLT 跳转、长跳转
X18平台保留寄存器部分平台用于指向 per-CPU 数据
X29帧指针 FP函数调用栈回溯
X30链接寄存器 LR保存返回地址
SP_EL0–SP_EL3各异常等级独立栈指针切换 EL 时自动切换栈

注意:AArch64 中 PC 不再是可直接读写的通用寄存器,异常返回地址保存在 ELR_ELx 中,通过 ERET 恢复执行。

四、系统寄存器与内存模型

系统寄存器通过 MRS(读)和 MSR(写)访问,命名格式为 寄存器名_ELx。启动代码中最关键的系统寄存器包括:

  • SCTLR_ELx:系统控制寄存器,控制 MMU、Cache、对齐检查等全局开关。
  • TCR_ELx:翻译控制寄存器,定义页表基址范围、颗粒度、IPA 范围。
  • TTBR0_ELx / TTBR1_ELx:页表基址寄存器,分别对应用户空间与内核空间。
  • VBAR_ELx:异常向量基址寄存器,决定异常入口地址。
  • DAIF:中断屏蔽位,控制 IRQ、FIQ、SError、Debug 异常。
  • SPSR_ELx / ELR_ELx:异常返回时的状态与地址保存。

ARM64 采用弱内存模型,处理器和编译器都可能对内存访问重排序。启动阶段在使能 MMU、写入页表、配置设备寄存器时,必须使用屏障指令保证顺序:

dsb sy      // 数据同步屏障,等待所有内存访问完成
isb         // 指令同步屏障,刷新流水线
dmb sy      // 数据内存屏障,保证访问顺序
tlbi vmalle1 // 失效 EL1 阶段所有 TLB 条目
常见误区:在写入 TTBR0_EL1 后立即使能 MMU,却未执行 dsb 与 isb,导致页表尚未对处理器可见就进行地址翻译,引发不可预期的取指异常。正确顺序是:写页表基址 → dsb → tlbi → dsb → isb → 使能 MMU。

五、启动场景中的综合运用

以典型的 BL31 启动为例,代码从 EL3 入口开始,首先读取 CurrentEL 确认当前异常等级,随后设置 VBAR_EL3 指向异常向量表,配置 SCTLR_EL3 关闭 MMU 与 Cache,再通过 SCR_EL3 配置安全世界路由,最后构造 SPSR_EL2 与 ELR_EL2,执行 ERET 下降至 EL2。整个过程体现了异常等级、系统寄存器与内存屏障三者的协同。

复盘建议:阅读任何一段 ARM64 启动汇编时,先问三个问题——当前在哪个 EL?关键系统寄存器是否已配置?跨 EL 跳转前后的屏障是否完整?这三问能快速定位大多数启动挂死问题。