2. 嵌入式系统启动流程:从复位向量到操作系统加载

嵌入式系统上电后,CPU 到底干了什么?

很多人觉得启动就是跑个 main 函数。其实没那么简单。从复位引脚拉高那一刻起,到操作系统开始调度第一个任务,中间有一整套严谨的流程。我做了这么多年嵌入式,见过不少项目栽在启动阶段——不是内存没配好,就是启动介质选错了。

这一节,我们就把启动流程彻底拆开看。

2.1 复位向量:CPU 的第一口饭

CPU 上电或复位后,PC 指针会被硬件强制指向一个固定地址。这个地址就是复位向量。

不同架构的复位向量地址不一样:

架构 复位向量地址 说明
ARM Cortex-M 0x00000000(向量表基址) 栈顶指针 + 复位中断入口
ARM Cortex-A 0x00000000 或 0xFFFF0000 由硬件配置引脚决定
RISC-V 0x80000000(典型值) 可配置,常见于 Boot ROM
x86 0xFFFFFFF0 复位后进入实模式

复位向量处放的通常是一条跳转指令,或者直接就是启动代码的入口地址。CPU 从这里开始,才算是真正「活」过来了。

我的习惯: 拿到一个新板子,第一件事就是看复位向量表。用调试器读一下 0x00000000 处的值,就能确认芯片是否正常启动。如果读出来全是 0xFF 或 0x00,八成是焊接或电源有问题。

2.2 ROM Code:芯片出厂自带的「最小引导」

现代 SoC 内部都有一小块 Mask ROM,出厂时固化了第一级启动代码。这就是 ROM Code(也叫 BootROM)。

ROM Code 的任务很简单:

  1. 初始化最基本的硬件(时钟、片内 SRAM)
  2. 检测启动介质选择引脚
  3. 从选中的介质读取下一级代码到 SRAM
  4. 跳转到该代码执行

ROM Code 是只读的,用户改不了。所以它的代码必须极其精简、稳定。我见过某款芯片的 ROM Code 有 bug,导致特定批次的 NAND Flash 无法启动——那真是欲哭无泪,只能改硬件方案。

注意: ROM Code 通常只支持固定的启动介质列表。如果你用的启动设备不在支持列表里,就得先通过其他方式(比如 JTAG)把代码烧进去。

2.3 启动介质选择:CPU 怎么知道从哪启动?

芯片一般有多个 BOOT 模式选择引脚(比如 BOOT0、BOOT1)。上电时,ROM Code 会采样这些引脚的电平,然后决定从哪个介质加载代码。

常见的启动模式:

BOOT 引脚配置 启动介质 典型场景
00 NOR Flash(片选 0) XIP 执行,快速启动
01 NAND Flash 大容量存储,成本低
10 SD 卡 / eMMC 消费电子,可插拔
11 网络(以太网 / USB) 调试阶段,无本地存储

我个人习惯在产品设计阶段,把 BOOT 引脚通过 10k 电阻上拉或下拉,而不是直接接电源或地。这样万一需要改启动模式,飞线就能解决,不用动 PCB。

2.4 四种主流启动模式详解

2.4.1 NOR Flash 启动

NOR Flash 支持 XIP(eXecute In Place),也就是代码可以直接在 Flash 上运行,不需要拷贝到 RAM。启动速度最快,但 NOR 容量小、价格高。

我曾经在一个工业控制器项目里用 NOR 启动,整个 Bootloader 只有 64KB,上电到进入主程序不到 200ms。客户非常满意。

2.4.2 NAND Flash 启动

NAND 不能 XIP,必须把代码先读到 RAM 里再执行。而且 NAND 有坏块问题,ROM Code 必须包含简单的 ECC 校验和坏块跳过逻辑。

这里有个坑:有些芯片的 ROM Code 只支持 2KB 页大小的 NAND,如果你用了 4KB 页的 NAND,启动就会失败。选型时一定要查芯片手册的 NAND 兼容列表。

2.4.3 SD 卡 / eMMC 启动

SD 卡启动在开发阶段特别方便。把编译好的镜像拷进 TF 卡,插上就能跑。ROM Code 会读取 SD 卡第一个扇区(MBR 或 GPT),找到启动分区,然后加载代码。

我记得有一次调试,SD 卡怎么都启动不了。折腾了半天,发现是分区表格式不对——ROM Code 只认 MBR,不认 GPT。换成 MBR 就好了。

2.4.4 网络启动

网络启动一般用于调试或量产阶段。ROM Code 通过 DHCP 获取 IP,然后从 TFTP 服务器下载镜像。这种方式不需要烧录,适合频繁修改代码的场景。

但要注意:网络启动依赖外部服务器,如果网络不通,板子就变砖了。所以量产产品很少用纯网络启动,通常是网络 + 本地 Flash 双备份。

核心原则: 启动介质的选择,本质是速度、成本、可靠性的三角权衡。没有最好的方案,只有最适合你项目的方案。

2.5 从 Bootloader 到操作系统加载

ROM Code 加载完第一级 Bootloader(SPL)后,SPL 会初始化 DDR 等大容量内存,然后从启动介质加载完整的 Bootloader(如 U-Boot)。

U-Boot 启动后,会做以下几件事:

  1. 初始化更多外设(网卡、存储控制器)
  2. 读取环境变量(bootargs、bootcmd)
  3. 从指定分区加载内核镜像(zImage / Image)
  4. 解压内核(如果压缩了)
  5. 跳转到内核入口点

内核启动后,会挂载根文件系统,然后执行 init 进程。到这一步,操作系统才算真正跑起来了。

整个流程可以用下面这张图概括:

嵌入式系统启动流程 上电 / 复位 CPU 读取复位向量 ROM Code 执行 检测 BOOT 引脚 加载 SPL / 第一级 初始化 DDR 加载 U-Boot 加载内核 + 根文件系统 说明:不同芯片的 ROM Code 实现有差异,但整体流程一致

2.6 避坑指南:启动阶段常见问题

  • 复位向量被擦除: 我曾经在调试时不小心把 NOR Flash 前 4KB 擦掉了,结果板子变砖。后来用 JTAG 重新烧录才救回来。建议量产时把复位向量区域写保护。
  • 启动介质时序不匹配: NAND Flash 的时序参数必须和 ROM Code 匹配。如果芯片手册说支持某款 NAND,实际却启动不了,先检查时序配置。
  • BOOT 引脚电平不稳定: 如果 BOOT 引脚悬空,上电时电平可能抖动,导致 ROM Code 误判启动介质。一定要加固定电平。
  • SD 卡兼容性问题: 不同品牌的 SD 卡,初始化时序略有差异。建议用同一品牌、同一批次的卡做量产。
调试小技巧: 用示波器抓 BOOT 引脚电平,同时抓 ROM Code 的片选信号。如果片选信号没拉低,说明 ROM Code 根本没尝试访问该介质。这是定位启动问题最快的方法。