2. Bootloader原理与启动流程
做嵌入式Linux开发,Bootloader是绕不开的第一道坎。很多新手上来就急着移植U-Boot,结果板子点不亮,连串口打印都看不到。说白了,Bootloader就是硬件和操作系统之间的桥梁,它负责把内核从存储介质里捞出来,放到内存里,然后跳过去执行。
我个人习惯把Bootloader比作「系统助产士」——它自己不干活,但没它,内核根本生不出来。今天我们就从最底层开始,把Bootloader的作用、SoC的启动顺序、以及多级启动链彻底讲透。
2.1 Bootloader到底干了什么
Bootloader的核心任务其实就三件:
- 硬件初始化:关看门狗、设置时钟、初始化DDR控制器、配置串口
- 加载内核:从Flash、SD卡、网络等介质读取内核镜像到内存
- 传递启动参数:把设备树、内核命令行参数交给内核
嗯,这里要注意——Bootloader不是操作系统,它没有进程调度、没有内存管理。它就是个裸机程序,跑完就死。我在项目中遇到过有人试图在U-Boot里加多线程,这完全是走错了方向。
关键认知:Bootloader的生命周期从SoC复位开始,到内核入口点结束。之后Bootloader占用的内存可以被内核回收。
2.2 SoC的启动顺序:从ROM Code到U-Boot
现在的SoC越来越复杂,启动过程也分成了好几个阶段。你想想看,芯片一上电,DDR都还没初始化,代码往哪跑?所以必须有个「从简到繁」的过程。
典型的启动链是这样的:
- ROM Code:固化在芯片内部ROM里,上电后CPU直接从这里取指
- SPL (Secondary Program Loader):由ROM Code加载到内部SRAM,初始化DDR
- U-Boot:由SPL加载到DDR,完成完整初始化,加载内核
为什么搞这么复杂?说白了,芯片内部SRAM太小了,装不下完整的U-Boot。ROM Code又没法改,所以只能分层接力。
| 启动阶段 | 存储位置 | 运行空间 | 大小限制 | 主要任务 |
|---|---|---|---|---|
| ROM Code | 芯片内部ROM | 内部ROM | 几十KB | 初始化时钟、检测启动介质、加载SPL |
| SPL | Flash/SD卡 | 内部SRAM | 通常≤64KB | 初始化DDR、加载U-Boot |
| U-Boot | Flash/SD卡 | DDR | 几百KB~几MB | 完整硬件初始化、加载内核 |
经验之谈:我刚开始做i.MX6的移植时,SPL死活跑不起来。后来发现是ROM Code读取启动设备时,SD卡的分区表格式不对。很多SoC的ROM Code只认特定偏移位置的镜像,这个坑踩一次就记住了。
2.3 多级启动链深度分析
我们拿一个典型的ARM Cortex-A系列SoC来拆解。假设板子上电,按下复位键,接下来会发生什么?
2.3.1 第一级:ROM Code
ROM Code是芯片出厂就烧好的,用户改不了。它做的事情很固定:
- 设置CPU为SVC模式,关中断
- 初始化最基本的时钟(通常是内部振荡器)
- 读取启动引脚的电平状态,判断从哪个设备启动
- 从启动设备读取SPL到内部SRAM
- 跳转到SPL入口
我曾经在调试一块新板子时,串口死活没输出。查了两天才发现,ROM Code默认的启动引脚电平被板级上拉电阻拉错了,它一直在尝试从QSPI启动,而我根本没焊Flash。嗯,这种硬件和软件打架的问题最头疼。
2.3.2 第二级:SPL
SPL是U-Boot的精简版,也叫TPL在某些架构里。它的大小被严格限制,因为内部SRAM通常只有64KB~256KB。SPL要干的事:
- 设置CPU频率和总线时钟
- 初始化DDR控制器(这是最关键的)
- 初始化存储设备控制器(比如SDHC、NAND控制器)
- 从存储设备读取完整的U-Boot到DDR
- 跳转到U-Boot入口
避坑指南:DDR初始化参数必须和板子的硬件设计完全匹配。我曾经遇到过SPL能加载U-Boot,但U-Boot一运行就崩溃。最后发现是DDR的时序参数配错了,SPL阶段因为代码小侥幸跑通,U-Boot一加载大镜像就触发数据错误。
2.3.3 第三级:U-Boot
到了U-Boot这一级,DDR已经可用,空间不再是瓶颈。U-Boot会做:
- 完整的板级初始化(网卡、USB、显示等)
- 解析环境变量(bootargs、bootcmd等)
- 支持交互式命令行
- 加载内核和设备树到DDR
- 跳转到内核入口
你想想看,U-Boot其实是个小型的操作系统——它有文件系统驱动、网络协议栈、命令行解析器。但它没有进程调度,所有操作都是顺序执行的。
2.4 启动流程全景图
下面这张图把整个启动链串起来了。我建议你把它打印出来贴在工位上,调试时对照着看,能省不少时间。
2.5 实际调试中的关键点
讲完了理论,说点实战中容易出问题的地方。我总结了几个常见坑:
- 串口输出时机:ROM Code阶段通常没有串口输出,SPL早期也没有。如果板子完全没打印,先检查硬件连接和时钟配置
- 启动介质选择:很多SoC通过Boot Mode引脚选择启动设备,焊接时一定要确认电平正确
- 镜像位置偏移:ROM Code读取SPL时,对偏移地址有严格要求。比如SD卡通常要求SPL放在第1个扇区(偏移512字节)
- DDR参数:这是移植中最容易翻车的地方。建议先用芯片厂商提供的DDR初始化代码,稳定后再优化
调试技巧:我习惯在SPL和U-Boot的入口处加GPIO翻转代码。用示波器看GPIO电平变化,比看串口打印更可靠——毕竟串口驱动可能还没初始化好。
好了,Bootloader的启动流程就讲到这里。理解了ROM Code、SPL、U-Boot这三级的接力关系,后面做移植时你就能清楚地知道:问题出在哪个阶段,该去查哪部分代码。