Bootloader 原理与启动流程
Bootloader,说白了就是嵌入式系统上电后跑的第一段程序。它的任务很明确:把硬件从「混沌状态」带到「可执行状态」,然后跳转到操作系统或用户应用。
我刚开始做嵌入式时,总觉得 Bootloader 就是个跳板,随便写写就行。直到有一次,板子死活起不来,查了三天才发现是 DDR 初始化时序差了几个纳秒。嗯,从那以后我再也不敢小看它了。
Bootloader 的核心作用
Bootloader 到底干了哪些事?我把它拆成三个层面:
| 层面 | 具体工作 | 我的经验 |
|---|---|---|
| 硬件初始化 | 设置时钟、配置 DDR、初始化串口/网口/存储控制器 | 时钟配置错了,整个系统跑飞,连打印都看不到 |
| 系统引导 | 从 Flash/NAND/eMMC 读取内核镜像,加载到内存 | NAND 坏块处理没做好,镜像读到一半就挂了 |
| 交互与调试 | 提供命令行、下载固件、设置启动参数 | 串口波特率不对,你敲什么它都回乱码 |
一句话总结:Bootloader 是硬件和软件的「翻译官」。它把硬件的原始状态翻译成操作系统能理解的「就绪状态」。
典型启动流程:ROM → RAM
大多数嵌入式芯片的启动流程是分阶段的。为什么不能一步到位?因为芯片上电时,内部 SRAM 很小,外部 DDR 还没初始化,你根本没法跑大程序。
我画了一张流程图,帮你理清这个路径:
我的习惯:调试时我会在每一步加一个 GPIO 翻转或串口打印。这样能快速定位是卡在 ROM 阶段、SRAM 阶段还是 DDR 阶段。别等到板子黑了才去猜问题。
多阶段启动机制
为什么需要多阶段?我给你讲个真实场景。
我曾经在一个项目里用了一颗 Cortex-A7 芯片,内部 SRAM 只有 64KB。而 U-Boot 完整版编译出来有 300KB 以上。你想想看,64KB 怎么装得下 300KB?
所以必须拆成两段:
- 第一阶段(SPL/MLO):体积小,跑在 SRAM 里,只做最核心的事——初始化 DDR。
- 第二阶段(U-Boot proper):体积大,跑在 DDR 里,做文件系统、网络、命令行等复杂功能。
这个机制在主流芯片上几乎成了标配:
| 阶段 | 存储位置 | 运行位置 | 典型大小 | 主要任务 |
|---|---|---|---|---|
| ROM Boot | 芯片内部 ROM | 芯片内部 ROM | 4KB ~ 32KB | 初始化时钟、检测启动介质、加载 SPL |
| SPL(Secondary Program Loader) | Flash / NAND / SD 卡 | 内部 SRAM | 16KB ~ 64KB | 初始化 DDR、加载 U-Boot proper |
| U-Boot proper | Flash / NAND / SD 卡 | 外部 DDR | 200KB ~ 1MB | 文件系统、网络、命令行、加载内核 |
我曾经踩过的坑:SPL 阶段如果 DDR 初始化参数配错了,U-Boot proper 加载到 DDR 后,一执行就崩溃。而且这时候串口还没完全初始化,你连错误信息都看不到。我的建议是:在 SPL 阶段先初始化串口,哪怕只输出一个字符,也能确认 SPL 跑起来了。
多阶段启动的典型代码结构
以 U-Boot 为例,它的启动代码分两个目录:
u-boot/
├── arch/arm/cpu/armv7/start.S # SPL 入口,汇编代码
├── board/your_board/spl.c # SPL 板级初始化
├── common/spl/spl.c # SPL 框架代码
├── common/spl/spl_nand.c # 从 NAND 加载
├── common/spl/spl_mmc.c # 从 SD/eMMC 加载
├── common/spl/spl_spi.c # 从 SPI Flash 加载
└── common/spl/spl_ram.c # 从 RAM 加载(调试用)
SPL 的执行流程大致是:
- start.S:设置异常向量、关闭中断、初始化栈指针。
- board_init_f:初始化时钟、串口、定时器。
- spl_load_image:从存储介质读取 U-Boot proper 到 DDR。
- jump_to_image_no_args:跳转到 DDR 中的 U-Boot proper 入口。
关键点:SPL 阶段不要做复杂操作。我见过有人把文件系统解析放到 SPL 里,结果 SPL 体积超了 SRAM 大小,直接编译不过。记住:SPL 只做「最小必要初始化」,其他都交给 U-Boot proper。
启动介质的选择与优先级
不同芯片支持的启动介质不一样。以 i.MX6 为例,它的启动顺序由 eFUSE 或 BOOT_CFG 引脚决定:
| 启动介质 | 典型场景 | 优点 | 缺点 |
|---|---|---|---|
| NAND Flash | 工业控制、低成本设备 | 容量大、成本低 | 有坏块、速度慢 |
| eMMC | 消费电子、高端设备 | 速度快、坏块管理内置 | 成本高、焊接要求高 |
| SPI NOR Flash | 小容量、快速启动 | XIP(原地执行)、可靠性高 | 容量小(通常 16MB 以下) |
| SD 卡 | 开发调试、量产烧录 | 可插拔、方便更新 | 可靠性不如焊接介质 |
我个人习惯在开发阶段用 SD 卡启动,调试方便,改完代码直接插卡就能测。量产时再切到 eMMC 或 NAND。
调试 Bootloader 的常见问题
最后分享几个我实际遇到过的坑:
- 串口无输出:先检查时钟配置,再检查 GPIO 复用。我遇到过 GPIO 复用没配对,串口物理上就没连到 SoC。
- DDR 初始化失败:用芯片厂商提供的 DDR 工具生成参数,别自己手算。手算出错的概率太高了。
- SPL 加载 U-Boot proper 后跑飞:检查链接地址。U-Boot proper 的链接地址必须和 DDR 中加载的地址一致。
- 启动介质读取超时:检查时序。NAND 的时序参数配错了,读出来的数据全是 0xFF。
我的调试三板斧:1)串口打印,能打一个字就说明 CPU 活了;2)GPIO 翻转,用示波器看电平变化;3)JTAG 单步,定位具体哪条指令崩了。这三招用下来,90% 的 Bootloader 问题都能找到根因。