启动流程基础:嵌入式系统的典型启动链

做嵌入式底层开发,第一关就是搞懂系统是怎么“活过来”的。你按下复位键,到内核开始跑第一个进程,中间发生了什么?

我刚开始接触这块时,觉得不就是上电跑代码嘛,有什么难的。结果第一次调板子,串口死活没输出,查了三天才发现是启动介质选错了。嗯,从那以后我再也不敢小看启动流程了。

2.1 典型启动链:四段跳

嵌入式系统的启动,说白了就是一段接力跑。每一棒只干一件事:把下一棒的代码从存储介质搬到内存,然后跳过去执行。典型的链式结构是这样的:

ROM Code → SPL → U-Boot → Kernel

这是目前最主流的启动路径,尤其在 TI、NXP、Rockchip 等平台上广泛使用。

为什么需要这么多阶段?因为芯片上电时,内部 SRAM 很小,外部 DDR 还没初始化,你根本跑不了完整的 Bootloader。所以必须分阶段来,每一级逐步“解锁”更大的内存和更复杂的硬件。

第一阶段:ROM Code

这是固化在芯片内部 ROM 里的一段代码,出厂就写死了,你改不了。它的任务很简单:

  • 初始化最基本的硬件(时钟、引脚、看门狗)
  • 从预设的启动介质读取下一级代码
  • 验证签名(如果使能了安全启动)
  • 跳转到 SPL

ROM Code 通常只有几十 KB,运行在芯片内部的 SRAM 里。我遇到过最坑的一次,是某款芯片的 ROM Code 对 NAND Flash 的时序要求特别苛刻,换了个不同品牌的 NAND 就起不来了——最后只能改硬件匹配电阻。

第二阶段:SPL(Secondary Program Loader)

SPL 是 U-Boot 的一个精简版本,也叫 MLO 或 x-loader。它的大小被严格限制,通常不能超过芯片内部 SRAM 的大小(比如 64KB 或 128KB)。

SPL 的核心任务:

  • 初始化外部 DDR 内存
  • 初始化存储控制器(NAND、eMMC、SD 卡等)
  • 从存储介质加载完整的 U-Boot 到 DDR
  • 跳转到 U-Boot

我的经验:SPL 阶段最容易出问题的是 DDR 初始化。DDR 的时序参数、电压、频率,任何一个不对,板子就死在这里。我习惯在 SPL 里加一段 DDR 读写测试,打印出来确认内存没问题再往下走。

第三阶段:U-Boot

完整的 U-Boot 是功能最丰富的 Bootloader。它运行在 DDR 里,空间充裕,可以做很多事情:

  • 提供命令行交互(你可以打断启动,手动输入命令)
  • 加载设备树(DTB)
  • 从各种介质加载内核(网络、USB、存储)
  • 设置内核启动参数(bootargs)
  • 最终跳转到内核

U-Boot 的灵活性很高,但也意味着配置项多。我见过不少团队在 U-Boot 里塞了太多驱动,导致启动时间超过 10 秒——其实很多外设根本用不上,裁剪掉就好。

第四阶段:Kernel

内核拿到控制权后,会做真正的系统初始化:

  • 解压自身(如果是压缩内核)
  • 初始化内存管理、进程调度、中断系统
  • 加载设备驱动
  • 挂载根文件系统
  • 启动第一个用户进程(init)

到了这一步,系统就算真正跑起来了。你可以在串口看到内核的启动日志,一条条刷过去,心里踏实。

2.2 启动介质:从哪里读代码

ROM Code 怎么知道去哪里找 SPL?这取决于启动模式的选择。芯片会检测某些引脚的电平,或者读取 OTP(一次性可编程)区域,来决定从哪个介质启动。

常见的启动介质有四种,我列个表对比一下:

介质 速度 可靠性 成本 典型场景
NAND Flash 中等 低(有坏块) 低 消费电子、低成本方案
eMMC 快 高(内置坏块管理) 中等 工业控制、车机、平板
SD 卡 中等 中等 低 开发调试、单板计算机
串口(UART) 极慢 高 无额外成本 工厂烧录、紧急恢复

NAND Flash

NAND 的特点是便宜、容量大,但天生有坏块。ROM Code 读 NAND 时,必须处理坏块跳过和 ECC 校验。我踩过最大的坑是:某款 NAND 的页大小是 4KB,但 ROM Code 只支持 2KB 页,结果读出来的数据全是错的——换了个兼容型号才解决。

eMMC

eMMC 内部集成了控制器,坏块管理、磨损均衡都由它自己处理。对 Bootloader 来说,eMMC 就像一块简单的块设备,读写操作比 NAND 省心很多。我个人习惯在产品量产时优先选 eMMC,虽然贵一点,但省下的调试时间远不止这个差价。

SD 卡

SD 卡和 eMMC 协议类似,但它是可插拔的。开发阶段用 SD 卡启动非常方便,改完代码直接插卡就能测。不过量产时很少用 SD 卡,因为接触不良会导致启动失败。

串口(UART)

串口启动是一种“救急”方式。当所有存储介质都坏了,或者板子上还没焊 Flash 时,可以通过串口把代码一点点灌进去。速度很慢(通常 115200 bps),传一个 64KB 的 SPL 要好几秒。但关键时刻,它能救命。

注意:串口启动通常需要芯片支持,并且 ROM Code 里要有对应的驱动。不是所有芯片都支持串口启动,选型时一定要确认。

2.3 启动流程全景图

下面这张图把整个启动链串起来了。你可以看到每一级干了什么,以及它们之间的跳转关系。

嵌入式系统典型启动流程 ROM Code 固化在芯片内部 初始化时钟/引脚 SPL 初始化DDR 加载U-Boot到内存 U-Boot 命令行交互 加载内核/DTB Kernel 系统初始化 挂载根文件系统 启动介质选择 ROM Code 根据引脚/OTP 选择介质 NAND | eMMC | SD卡 | 串口 → 读取 SPL 到内部 SRAM 关键说明: • ROM Code 运行在内部 SRAM,大小通常 64KB 以内 • SPL 也运行在 SRAM,负责初始化 DDR 并加载 U-Boot • U-Boot 运行在 DDR,提供完整功能并加载内核 • 内核启动后,Bootloader 使命完成,系统进入用户态

2.4 启动模式的选择与切换

芯片怎么知道用哪种介质启动?通常有两种方式:

  • 硬件引脚:芯片有专门的 BOOT 引脚,上电时采样电平,决定启动顺序。比如拉高从 eMMC 启动,拉低从 SD 卡启动。
  • OTP/efuse:一次性编程区域,出厂时烧录好,之后不能改。适合量产产品,防止被篡改。

有些芯片支持“回退”机制:如果首选介质读不到数据,自动切换到下一个。比如先试 eMMC,失败后试 SD 卡,再失败就进串口下载模式。这个功能在开发调试时特别有用——你想想看,如果板子焊错了 Flash,至少还能从串口救回来。

我的建议:开发阶段把启动顺序设为 SD 卡优先,eMMC 次之。这样你改完代码,插卡就能测,不用反复烧写 eMMC。等产品定型了,再改成 eMMC 优先,并锁死 OTP。

2.5 避坑指南

做启动流程调试,有几个坑我反复踩过,分享给你:

  • DDR 时序不对:SPL 阶段 DDR 初始化失败,板子直接死掉。解决办法是仔细看芯片手册的 DDR 配置部分,或者用芯片厂商提供的 DDR 工具生成参数。
  • 启动介质顺序搞反:我曾经把 eMMC 和 SD 卡的启动引脚接反了,结果 ROM Code 一直从空介质读数据,板子毫无反应。查了两天才发现是原理图画错了。
  • 签名验证失败:如果使能了安全启动,ROM Code 会校验 SPL 的签名。签名不对,直接停在这里。我习惯在开发阶段先关掉安全启动,等所有功能调通了再打开。
  • 串口启动波特率不匹配:有些芯片的 ROM Code 串口下载固定用 115200,但你的终端软件设成了 9600——看到乱码还以为代码传错了。先确认波特率,再怀疑人生。

启动流程是整个嵌入式系统的基石。搞懂了这一块,后面调分区、调启动链联调,你心里就有底了。