1. Bootloader 基础概念:它到底在干什么?
说实话,很多刚入行的朋友觉得 Bootloader 就是个「黑盒子」——上电后闪一下,然后内核就起来了。但我在做嵌入式这十几年里,踩过最大的坑,恰恰就是没把 Bootloader 的启动逻辑吃透。
Bootloader 说白了,就是硬件上电后运行的第一段软件。它的任务很纯粹:把硬件初始化到可用状态,然后加载并跳转到操作系统。
核心职责一句话:从「CPU 复位」到「内核入口点」之间的所有脏活累活。
1.1 Bootloader 到底干了哪些事?
我习惯把 Bootloader 的工作拆成四个阶段,这样调试时思路特别清晰:
- 最底层硬件初始化——时钟、DDR、串口。没有这一步,后面全是空谈。
- 建立运行时环境——设置栈指针、清零 BSS 段、搬移自身到 RAM。
- 加载内核映像——从 Flash、SD 卡、网络或 USB 读数据。
- 传递启动参数并跳转——把设备树、cmdline 交给内核,然后一去不返。
嗯,这里要注意:第 4 步一旦跳转,Bootloader 就「死」了。内核接管后,Bootloader 占用的内存可以被回收。这个设计理念我一直觉得挺优雅的。
1.2 ARM 平台的启动流程——从复位向量开始
ARM 芯片上电后,CPU 会从复位向量取指。这个地址通常是 0x00000000 或 0xFFFF0000,具体看 SoC 设计。我在项目中遇到过一块芯片,复位向量居然在 ROM 里,而且 ROM 还做了加密校验——当时折腾了整整两天才把第一行代码跑通。
典型的 ARM 启动流程大致是这样:
- ROM Code(固化在芯片内部):厂商写死的,负责从 Boot 设备(SD卡、NAND、SPI Flash)读取第一段代码到 SRAM。
- SPL / MLO(Secondary Program Loader):因为 SRAM 通常只有几十 KB,放不下完整的 U-Boot。SPL 负责初始化 DDR,然后把完整的 U-Boot 从存储设备搬进内存。
- U-Boot 主程序:完整的 Bootloader,提供命令行、网络、文件系统支持,最终加载内核。
- ATF(ARM Trusted Firmware):如果芯片支持安全启动或 EL3 异常等级,ATF 会在 U-Boot 之前运行,建立安全世界。
个人经验:调试启动流程时,我习惯在 SPL 阶段就点亮一颗 LED。这样能最快判断「代码到底有没有跑起来」。别笑,这招救过我很多次。
下面这张图是我自己画的一个简化启动流程,你可以对照着理解:
1.3 主流 Bootloader 对比——选型是个技术活
市面上 Bootloader 不少,但真正在 ARM 平台广泛使用的,其实就三个。我列个表,你一看就明白:
| 特性 | U-Boot | ARM Trusted Firmware (ATF) | Barebox |
|---|---|---|---|
| 定位 | 通用 Bootloader | 安全启动 / EL3 固件 | 轻量级 Bootloader |
| 架构支持 | ARM、x86、RISC-V 等 | 仅 ARMv8-A / Cortex-A | ARM、MIPS、x86 |
| 代码规模 | 大(约 50 万行) | 中等(约 10 万行) | 小(约 5 万行) |
| 命令行 | 功能丰富,类 Shell | 无(仅固件接口) | 类似 Linux Shell |
| 设备树支持 | 原生支持 | 有限 | 原生支持 |
| 社区活跃度 | 极高 | 高(ARM 官方维护) | 较低 |
| 典型场景 | 嵌入式 Linux 产品 | 安全启动 / 虚拟化 | 资源受限系统 |
避坑指南:我曾经在一个项目里选了 Barebox,因为它「轻量」。结果产品中期发现需要支持安全启动,Barebox 没有现成的 ATF 集成方案,最后硬着头皮把 U-Boot 和 ATF 一起移植上去,工期拖了一个月。选型时一定要想清楚未来 2-3 年的功能需求。
1.4 为什么我推荐从 U-Boot 入手?
你想想看,U-Boot 几乎支持市面上所有 ARM SoC——从老旧的 S3C2440 到最新的 Cortex-A78。它的代码虽然庞大,但分层清晰:
- board/:板级代码,移植时主要改这里
- arch/arm/:CPU 架构相关代码
- drivers/:外设驱动(串口、网卡、MMC 等)
- cmd/:命令行命令实现
我个人习惯是:拿到一块新板子,先看 board/ 目录下有没有类似的参考板。有的话直接复制一份改,没有的话才从头写。这个思路帮我省了不少时间。
小技巧:U-Boot 的 README 文件其实写得很好,尤其是 doc/README.imx 这类 SoC 专属文档。别只盯着代码看,文档里经常藏着关键寄存器配置。