很多人第一次看 ARM 启动流程,会觉得它只是“上电以后把系统拉起来”。真往里看就会发现,这里面是一串接力:先决定从哪启动,再把最小硬件环境搭起来,再把更大的代码搬进内存,最后才轮到内核或应用接手。
如果只抓最关键的问题,其实就三件:
- 第一条指令从哪里取出来
- 系统靠什么逐步把硬件初始化到“可跑复杂软件”的程度
- 控制权最终如何从 ROM 交给 Bootloader,再交给内核或应用
1. 从上电到取第一条指令
处理器上电后并不会“自动进入 Linux”,它首先只会做一件最基础的事:从预定义的复位向量地址开始取指执行。
这个入口通常由 SoC 启动 ROM 控制。启动 ROM 一般固化在芯片内部,职责很明确:
- 读取启动模式引脚或 eFuse 配置
- 判断当前应该从哪种介质启动
- 做非常少量的早期初始化
- 把下一阶段代码搬运到指定位置并跳转
常见启动介质包括:
- SPI Flash
- NAND / NOR Flash
- eMMC
- SD 卡
- USB 下载模式
- 网络启动
这一阶段代码很小,能做的事也有限,但它的优先级最高。这里一旦出问题,后面连 Bootloader 都轮不上。
2. 硬件初始化阶段到底在做什么
启动早期的硬件初始化不是“把所有外设都初始化一遍”,而是建立一个最小可运行环境。通常先保证这几类能力:
- 时钟已经工作正常
- 复位链路完成释放
- 片上 SRAM 可用
- 串口可输出日志
- 启动介质可读
很多芯片上电后只能先使用片上 SRAM,因为 DDR 此时还没有训练完成,也没有稳定可用。于是早期代码常常会采用“两段式启动”:
- 在片上 SRAM 中执行很小的一段初始化代码
- 初始化 DDR 后,再把更大的 Bootloader 主体搬到 DDR 中运行
因此,启动早期代码通常要重点处理:
- 时钟树和 PLL 设置
- 引脚复用
- UART 初始化
- 存储控制器初始化
- DDR training / 校准
所以很多启动问题最开始的现象就是“串口一点动静都没有”。不是日志没开,而是连串口这件事本身都还没准备好。
3. Boot ROM、SPL、U-Boot 之间的关系
在很多 ARM Linux 系统中,启动链条并不是单一的 Bootloader,而是多段式:
Boot ROM -> SPL / TPL -> U-Boot -> Linux Kernel
Boot ROM
芯片内部固化代码,负责选择启动介质和加载下一阶段镜像。
SPL
SPL 可以先把它当成“前置版 Bootloader”。早期可用的 SRAM 往往很小,完整的 U-Boot 根本放不下,所以需要先跑一个瘦身版阶段:
- 初始化 DDR
- 初始化基础串口
- 从更大的存储介质加载完整 Bootloader
U-Boot
U-Boot 是更完整的 Bootloader,职责通常包括:
- 初始化更多外设
- 提供命令行和调试能力
- 识别文件系统、分区表、镜像格式
- 加载 kernel、dtb、initramfs
- 组装 bootargs
- 跳转到内核入口
如果没有 SPL,U-Boot 也可以直接承担这些工作,但前提是芯片启动条件允许它被直接加载并运行。
4. 进入 Linux 内核前到底要准备什么
对于 Linux 场景,Bootloader 的目标不是“把所有系统功能都准备完”,而是把 Linux 内核启动所需的几个核心输入准备好:
- 内核镜像
- 设备树(dtb)
- 可选的 initramfs / initrd
- 启动参数 bootargs
其中设备树很关键,因为它告诉内核:
- 这块板子上有哪些外设
- 外设寄存器地址是什么
- 中断号、时钟、GPIO 怎么关联
- 内存布局是什么
Bootloader 在跳转前常见会做的事包括:
- 把内核镜像放到约定地址
- 把 dtb 放到约定地址
- 设置内核命令行,例如 rootfs、console、loglevel
- 清理或关闭不需要继承给内核的硬件状态
这一步如果参数错了,常见现象包括:
- 内核能启动,但找不到根文件系统
- 串口设备名不对,没有日志输出
- 设备树和板级硬件不匹配,驱动初始化异常
5. Linux 和 RTOS 的启动差异
Linux 场景
典型路径是:
上电 -> Boot ROM -> SPL/U-Boot -> Kernel -> init/systemd -> 用户空间
它的特点是:
- 启动链更长
- 需要准备设备树和根文件系统
- 内核初始化阶段更复杂
- 更适合复杂驱动、多任务和完整用户态系统
RTOS / Bare-metal 场景
典型路径通常更短:
上电 -> Boot ROM / Bootloader -> RTOS 或应用主程序
特点是:
- 组件更少
- 启动更快
- 镜像结构更简单
- 更强调实时性和可预测性
Linux 启动更像是在先把舞台搭好,RTOS 启动则更接近把程序直接拉起来干活。
6. 分析 ARM 启动问题时的排查顺序
启动问题最怕一上来就从内核日志开始猜。更稳的做法是按阶段切:
1. 先确认卡在哪一层
- 连 Boot ROM 都没走到
- SPL 没起来
- U-Boot 没起来
- Kernel 没起来
- Kernel 起了但用户空间没起来
2. 看最早日志从哪开始出现
如果第一条日志都没有,优先怀疑:
- 电源 / 时钟
- 启动介质
- 串口配置
- DDR 初始化前就崩溃
如果 U-Boot 正常但内核没起来,优先看:
- 镜像地址
- 设备树
- bootargs
- 根文件系统路径
3. 把“启动慢”拆成阶段时间
启动慢不一定是内核慢,也可能是:
- Bootloader 读取存储太慢
- DDR training 太久
- 文件系统挂载等待超时
- 用户态服务启动过多
按阶段记录时间点,比模糊地说“系统慢”更有价值。
7. 学 ARM 启动流程时最值得抓住的主线
如果要快速记住顺序,可以先记这个:
- Boot ROM 决定从哪里启动
- 早期代码建立最小硬件环境
- Bootloader 建立完整加载能力
- 内核接管硬件和内存管理
- 用户空间接管系统服务
真正理解之后,再去看具体芯片文档、U-Boot 启动日志、内核 start_kernel(),会容易很多。
8. 总结
ARM 启动流程说白了就是一场逐级接力:
- 第一棒解决“能不能启动”
- 第二棒解决“能不能把更大代码跑起来”
- 第三棒解决“能不能把系统内核装载完成”
- 第四棒解决“能不能进入真正的业务环境”
理解启动流程,不是为了背名字,而是为了出问题时能立刻判断:现在到底卡在哪一棒,上一棒做没做完,下一棒为什么没接上。