1. 课程导入
大家好,我是你们的讲师。做了十几年嵌入式底层,我始终觉得 Bootloader 是嵌入式系统里最容易被低估的角色。很多人觉得它不就是个引导程序嘛,把内核加载起来就完事了。但说实话,我见过太多项目因为 Bootloader 没配好,导致整块板子点不亮,最后查来查去,问题就出在 DDR 初始化时序或者设备树里一个 pinmux 配置错了。
这一章,我们先从宏观上把 Bootloader 的定位、启动流程和主流方案理清楚。你只有知道它“为什么存在”,后面做移植和调试时才能抓住重点。
1.1 Bootloader 在嵌入式系统中的角色
嵌入式系统上电后,CPU 会从一个固定的地址开始执行第一条指令。这个地址通常映射到片上的 ROM 或 Flash。但问题是——这时候 DDR 还没初始化,内核镜像还躺在 Flash 里,你总得有人先把硬件初始化好,再把内核从存储介质搬到内存里,最后跳过去执行。
这个“人”就是 Bootloader。
我习惯把 Bootloader 比作“系统看门人”。它负责三件事:
- 硬件初始化:时钟、DDR、串口、存储控制器……这些外设在上电后都是“裸”的,Bootloader 得把它们伺候好。
- 加载内核:从 Flash、SD 卡、网络等介质把内核镜像读到内存里。
- 传递启动参数:把设备树、内核命令行、ATAG 等信息交给内核。
关键认知:Bootloader 不是操作系统的一部分,它是独立于内核的裸机程序。你换内核版本,Bootloader 通常不需要动;但你换板子,Bootloader 几乎必须重配。
1.2 典型启动流程:ROM Code → SPL → U-Boot → Kernel
现在的嵌入式 SoC 越来越复杂,启动流程也分成了多个阶段。我拿最典型的 ARM 平台举例,整个链条是这样的:
- ROM Code:芯片出厂固化的只读代码。它很小,通常只有几十 KB。ROM Code 负责从预设的启动介质(比如 SD 卡、eMMC、NAND Flash)里读取下一级程序到内部 SRAM 里。为什么不用 DDR?因为 DDR 还没初始化,内部 SRAM 是唯一能用的 RAM。
- SPL (Secondary Program Loader):也叫 MLO 或 x-loader。它由 ROM Code 加载到 SRAM 里执行。SPL 的任务很简单:初始化 DDR 时钟和控制器,然后把完整的 U-Boot 从存储介质拷贝到 DDR 里。
- U-Boot:完整的 Bootloader。它提供命令行、环境变量、网络协议栈、文件系统支持。U-Boot 加载设备树(.dtb)和内核镜像(zImage/Image),然后跳转到内核入口。
- Kernel:内核接管后,Bootloader 的使命就结束了。内核会重新初始化所有外设,挂载根文件系统,启动用户空间。
嗯,这里要注意:有些 SoC 没有 SPL 阶段,ROM Code 直接加载 U-Boot。但大多数现代 SoC(比如 TI AM335x、NXP i.MX 系列)都采用这种三级启动,因为内部 SRAM 太小,装不下完整的 U-Boot。
下面这张图把整个流程串起来了,你可以对照着看:
我的经验:调试启动流程时,我习惯在 SPL 和 U-Boot 的串口输出里加一些自定义打印。比如在 DDR 初始化前后各打一个字符,这样能快速定位是 DDR 没配好,还是 U-Boot 根本没被加载进来。曾经有个项目,板子死活起不来,最后发现是 ROM Code 读取 SPL 时 CRC 校验失败——因为 SD 卡的分区表格式不对。
1.3 主流 Bootloader 对比:U-Boot vs Barebox vs RedBoot
市面上 Bootloader 不少,但真正在工业级产品里广泛使用的,也就那么几个。我列个表格,把它们的核心差异说清楚:
| 特性 | U-Boot | Barebox | RedBoot |
|---|---|---|---|
| 起源 | PPCBoot → U-Boot,2000 年左右 | U-Boot 分支,2010 年左右独立 | eCos 生态,2000 年左右 |
| 社区活跃度 | 极高,几乎每款 SoC 都支持 | 中等,主要支持 ARM 和 RISC-V | 低,基本停止维护 |
| 代码风格 | C 语言 + 少量汇编,宏定义多 | 更接近 Linux 内核风格,清晰 | C 语言,较老派 |
| 设备树支持 | 完整支持,从 2014 年起标配 | 完整支持,原生设计 | 不支持,需手动传参 |
| 命令行 | 功能强大,脚本支持好 | 类似 Linux shell,体验好 | 简单,功能有限 |
| 驱动模型 | DM (Driver Model),2016 年后引入 | 原生驱动模型,更现代 | 无统一模型 |
| 适用场景 | 绝大多数嵌入式 Linux 产品 | 对代码质量要求高的项目 | 遗留系统、eCos 生态 |
你想想看,现在做新项目,基本没人会选 RedBoot 了。它连设备树都不支持,这在今天简直是不可想象的。Barebox 其实是个好东西,代码写得比 U-Boot 干净,但生态差太远——你随便买个开发板,厂商给的 BSP 里肯定是 U-Boot,不会是 Barebox。
避坑指南:我曾经在一个项目里试图把 Barebox 移植到一款 NXP i.MX6UL 的板子上。Barebox 的代码确实漂亮,驱动分层清晰,但问题在于——网上几乎找不到现成的板级配置,所有 DDR 参数、时钟树、pinmux 都得自己从参考手册里抠。折腾了两周,最后还是换回了 U-Boot。不是说 Barebox 不好,而是生态决定了你的开发效率。
1.4 为什么我选择 U-Boot 作为课程主线
这门课的所有实战内容,我都会基于 U-Boot 来展开。原因很简单:
- 市场占有率:保守估计,80% 以上的嵌入式 Linux 产品在用 U-Boot。你出去面试,人家问的也是 U-Boot。
- 设备树支持成熟:U-Boot 从 2014 年开始全面拥抱设备树,现在已经是标配。设备树配置和调试,正是这门课的核心。
- 板级移植资料多:不管你是用 TI、NXP、Rockchip 还是 Allwinner 的 SoC,网上都能找到现成的 U-Boot 板级配置做参考。
- 调试工具链完善:U-Boot 的网卡、TFTP、NFS 支持非常稳定,调试内核时特别方便。
当然,U-Boot 也有缺点——代码量巨大,宏定义满天飞,阅读体验不太好。但话说回来,做底层开发,哪有不啃代码的?