3. 启动流程概览
ARM64 平台的启动并不是“上电即进 Linux”,而是一条由固化代码、可信固件、引导加载程序和内核接力完成的链路。每一级都只做自己职责范围内的事,然后把控制权交给下一级。理解这条链路的关键,是看清谁在什么时刻运行、运行在哪个异常级别、依赖哪些存储介质、把什么参数传给下一级。
典型链路可以概括为:BL1 → BL2 → BL31 → BL33(U-Boot)→ Linux 内核。其中 BL1/BL2/BL31 属于 ARM Trusted Firmware(ATF)体系,BL33 通常是 U-Boot 等通用引导程序,最后才进入内核。下面这张图展示了从上电复位到内核启动的完整信号与控制流。
核心原理:为什么需要多级接力
ARM64 上电后,CPU 从固定的复位向量取指,此时内存控制器尚未初始化,DDR 不可用,能运行的只有芯片内部 SRAM 和 Boot ROM。因此第一级代码必须足够小、足够可靠,这就是 BL1 存在的意义。BL1 完成最基础的 CPU 和存储初始化后,才有能力把更大的 BL2 从 Flash 搬到 SRAM 或已初始化的 DDR 中执行。
BL2 承担平台相关初始化,包括时钟树、DDR 训练、引脚复用、电源域配置等。这些工作与具体 SoC 强相关,放在 BL2 中可以与通用代码解耦。BL2 完成后,系统已经具备运行复杂固件和引导程序的条件,于是加载 BL31 和 BL33。
BL31 是常驻 EL3 的运行时固件,它不会退出,而是通过 SMC 异常为 EL1/EL2 提供电源管理、安全服务等。BL33 则是我们熟悉的 U-Boot,运行在非安全世界,负责加载内核镜像、设备树和 initrd,最终跳转到 Linux 内核。
方法流程:各阶段职责与交接参数
下面用清单方式梳理每一级的输入、输出和关键动作,便于对照实际启动日志排查问题。
- BL1:复位入口 → 初始化 CPU 和栈 → 从 Boot 介质读取 BL2 → 验签 → 跳转 BL2。
- BL2:平台初始化 → 配置 DDR → 加载 BL31 和 BL33 → 构建镜像描述符 → 跳转 BL31。
- BL31:建立 EL3 运行时 → 初始化 PSCI → 注册 SMC 处理 → 返回 BL33 入口。
- BL33(U-Boot):驱动初始化 → 加载内核/DTB/initrd → 设置启动参数 → 跳转内核。
- Linux 内核:解压 → 建立页表 → 解析 DTB → 初始化子系统 → 启动 init。
在 BL33 跳转内核时,ARM64 有明确的寄存器约定,常见如下:
/* U-Boot 跳转 Linux 内核时的寄存器约定 */
x0 = 设备树(DTB)在内存中的物理地址
x1 = 0
x2 = 0
x3 = 0
PC = 内核镜像入口地址
如果 x0 没有正确指向 DTB,内核启动早期就会因为找不到设备树而挂起或报错。这是实际调试中最常见的启动问题之一。
案例场景:一次典型的启动日志
以某 ARM64 开发板为例,串口输出的启动日志大致如下:
NOTICE: BL1: Booting BL2
NOTICE: BL2: Booting BL31
NOTICE: BL31: v2.8(release)
NOTICE: BL31: Initializing runtime services
NOTICE: BL31: Preparing for EL3 exit to normal world
NOTICE: BL33: U-Boot 2023.07
U-Boot: Loading kernel from mmc 0:1
U-Boot: Loading DTB from mmc 0:1
Starting kernel ...
[ 0.000000] Booting Linux on physical CPU 0x0
[ 0.000000] Linux version 6.1.0
从日志可以清楚看到 BL1、BL2、BL31、BL33 的接力顺序。如果卡在“BL2: Booting BL31”之后没有输出,通常说明 BL31 镜像加载地址或入口配置有误;如果卡在“Starting kernel ...”之后,则多半是 DTB 地址、内核入口或内存布局问题。
误区与复盘
常见误区一:认为 BL1 会直接加载内核。BL1 运行在 SRAM 中,容量和功能都极其有限,不可能直接解析文件系统或加载内核。它只负责把下一级引导起来。
常见误区二:忽略 BL31 的常驻特性。BL31 不是“执行完就退出”,而是常驻 EL3,内核运行期间仍会通过 SMC 调用它。因此 BL31 的内存区域不能被内核随意覆盖。
常见误区三:把 U-Boot 当成唯一引导程序。在 ATF 体系中,U-Boot 只是 BL33 的一种实现,也可以替换为其他引导程序,但交接约定必须保持一致。
排查启动问题时,建议按“BL1 → BL2 → BL31 → BL33 → Kernel”的顺序逐级确认:每一级是否有输出、是否成功跳转、交接参数是否正确。把这条链路画在纸上,对照日志逐段排除,比盲目改配置高效得多。
各阶段职责速查表
| 阶段 | 运行级别 | 核心职责 | 关键输出 |
|---|---|---|---|
| BL1 | EL3 | 复位入口、CPU/栈初始化、加载 BL2 | 跳转 BL2 |
| BL2 | EL3 | 平台初始化、DDR 配置、加载 BL31/BL33 | 镜像描述、跳转 BL31 |
| BL31 | EL3 | 常驻运行时、PSCI、SMC 服务 | 返回 BL33 入口 |
| BL33 | EL2/EL1 | 驱动初始化、加载内核/DTB/initrd | x0=DTB,跳转内核 |
| Linux | EL1 | 解压、页表、DTB 解析、子系统初始化 | 启动 init 进程 |
把这条链路理解清楚,后续分析每一级的镜像格式、加载地址、异常级别切换和参数传递时,就能准确定位问题发生在哪一段,而不是把整条启动流程当成一个黑盒。