3. 启动流程概览

ARM64 平台的启动并不是“上电即进 Linux”,而是一条由固化代码、可信固件、引导加载程序和内核接力完成的链路。每一级都只做自己职责范围内的事,然后把控制权交给下一级。理解这条链路的关键,是看清谁在什么时刻运行、运行在哪个异常级别、依赖哪些存储介质、把什么参数传给下一级。

典型链路可以概括为:BL1 → BL2 → BL31 → BL33(U-Boot)→ Linux 内核。其中 BL1/BL2/BL31 属于 ARM Trusted Firmware(ATF)体系,BL33 通常是 U-Boot 等通用引导程序,最后才进入内核。下面这张图展示了从上电复位到内核启动的完整信号与控制流。

ARM64 启动链路:从上电复位到 Linux 内核 BL1 Boot ROM EL3 · 复位入口 BL2 Trusted Firmware EL3 · 平台初始化 BL31 Runtime Firmware EL3 · PSCI/SMC BL33 U-Boot EL2/EL1 · 引导 Linux Kernel EL1 · 内核启动 init 进程 BL1 职责 • 复位向量入口 • 初始化 CPU/栈 • 加载 BL2 镜像 • 校验签名后跳转 BL2 职责 • 平台早期初始化 • 配置 DDR/时钟 • 加载 BL31/BL33 • 建立镜像描述 BL31 职责 • 常驻 EL3 运行 • PSCI 电源管理 • SMC 服务分发 • 异常向量接管 BL33 职责 • 设备驱动初始化 • 加载内核镜像 • 准备 DTB/initrd • 设置启动参数 内核职责 • 解压并自解压 • 建立页表/MMU • 解析 DTB 设备树 • 启动 init 进程 异常级别 EL3(BL1/BL2/BL31) EL2(Hypervisor) EL1(U-Boot/内核) EL0(用户态) 关键交接点与传递内容 ① BL1 → BL2:传递平台启动参数、镜像加载地址、安全启动状态 ② BL2 → BL31:传递 BL33 入口地址、设备树地址、内存布局信息 ③ BL31 → BL33:通过 SMC 返回,BL33 在 EL2/EL1 继续执行 ④ BL33 → Kernel:传递 x0=DTB 地址、x1~x3=0,跳转内核入口

核心原理:为什么需要多级接力

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 进程

把这条链路理解清楚,后续分析每一级的镜像格式、加载地址、异常级别切换和参数传递时,就能准确定位问题发生在哪一段,而不是把整条启动流程当成一个黑盒。