Bootloader 原理与启动流程

Bootloader,说白了就是嵌入式系统上电后跑的第一段程序。它的任务很明确:把硬件从「混沌状态」带到「可执行状态」,然后跳转到操作系统或用户应用。

我刚开始做嵌入式时,总觉得 Bootloader 就是个跳板,随便写写就行。直到有一次,板子死活起不来,查了三天才发现是 DDR 初始化时序差了几个纳秒。嗯,从那以后我再也不敢小看它了。

Bootloader 的核心作用

Bootloader 到底干了哪些事?我把它拆成三个层面:

层面 具体工作 我的经验
硬件初始化 设置时钟、配置 DDR、初始化串口/网口/存储控制器 时钟配置错了,整个系统跑飞,连打印都看不到
系统引导 从 Flash/NAND/eMMC 读取内核镜像,加载到内存 NAND 坏块处理没做好,镜像读到一半就挂了
交互与调试 提供命令行、下载固件、设置启动参数 串口波特率不对,你敲什么它都回乱码

一句话总结:Bootloader 是硬件和软件的「翻译官」。它把硬件的原始状态翻译成操作系统能理解的「就绪状态」。

典型启动流程:ROM → RAM

大多数嵌入式芯片的启动流程是分阶段的。为什么不能一步到位?因为芯片上电时,内部 SRAM 很小,外部 DDR 还没初始化,你根本没法跑大程序。

我画了一张流程图,帮你理清这个路径:

Bootloader 典型启动流程(ROM → RAM) ① 上电复位 ② ROM Boot(一级Boot) 初始化CPU、时钟、最小外设 ③ 加载二级Boot到SRAM 从Flash/NAND/eMMC读取SPL/MLO ④ 二级Boot初始化DDR 配置DDR控制器、时序、电压 ⑤ 加载内核/应用到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 的执行流程大致是:

  1. start.S:设置异常向量、关闭中断、初始化栈指针。
  2. board_init_f:初始化时钟、串口、定时器。
  3. spl_load_image:从存储介质读取 U-Boot proper 到 DDR。
  4. 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 问题都能找到根因。