启动流程概览:从复位向量到操作系统加载的完整链路

做嵌入式这些年,我调试过最多的就是系统起不来的问题。说白了,搞懂启动流程,就等于拿到了排查故障的钥匙。这一章,咱们把从芯片上电到操作系统跑起来的完整链路捋一遍。

你想想看,一个嵌入式设备按下电源键,到屏幕上出现登录提示符,中间到底发生了什么?我习惯把这个过程拆成四个阶段:硬件初始化 → Bootloader 引导 → 内核加载 → 根文件系统挂载。每个阶段都有坑,咱们一个一个说。

复位向量:一切从这里开始

芯片上电后,CPU 会去一个固定的地址取第一条指令。这个地址就叫复位向量。ARM Cortex-M 系列通常是 0x00000000,Cortex-A 系列可能是 0x00000000 或 0xFFFF0000,x86 则是 0xFFFFFFF0。

关键点:复位向量里存的不是代码,而是栈指针和复位中断服务函数地址。CPU 先加载栈指针,再跳转到复位函数执行。

我曾经遇到过一个项目,芯片死活不跑。查了半天,发现是复位向量表被意外擦除了。嗯,从那以后我每次烧录完都会先读一遍向量表确认。

启动流程四阶段详解

我把完整的启动链路画成了下面这张图,你先有个整体印象,我再逐段拆解。

嵌入式系统启动流程概览 嵌入式系统启动流程概览 阶段一 硬件初始化 阶段二 Bootloader 引导 阶段三 内核加载与启动 硬件初始化子步骤 1. 复位向量加载 2. 时钟/PLL 配置 3. DDR 初始化 4. 串口/调试口初始化 Bootloader 子步骤 1. 从存储介质加载 2. 自搬移/重定位 3. 设备树/硬件探测 4. 跳转到内核入口 内核启动子步骤 1. 解压内核镜像 2. 初始化内存管理 3. 驱动加载 4. 挂载根文件系统 每个阶段都可能成为启动失败的根源 调试时建议从后往前逐级排查

阶段一:硬件初始化

CPU 从复位向量跳出来后,第一件事就是初始化硬件。这个阶段通常由芯片内部 ROM 里的固化启动代码完成,或者由一级 Bootloader 负责。

  • 时钟系统配置:PLL 锁定、分频系数设置。我见过有人把 CPU 主频设得比 Flash 读取速度还快,结果系统跑飞了。
  • DDR 初始化:时序参数、刷新率、地址映射。这一步错了,后面所有代码都跑不起来。
  • 串口初始化:调试信息输出的基础。我个人习惯在 DDR 初始化完成后立刻打印一个字符,确认硬件通路正常。

注意:DDR 初始化参数和硬件布局强相关。换一颗不同容量的 DDR 颗粒,时序参数就得重新调。不要直接复制粘贴参考代码。

阶段二:Bootloader 引导

硬件初始化完成后,控制权交给 Bootloader。Bootloader 的核心任务就两个:从存储介质加载内核镜像,然后跳转到内核入口。

这里有个细节很多人忽略——自搬移。Bootloader 通常先把自己从 Flash 复制到 DDR 里运行,因为 Flash 的读取速度太慢了。我调试过一个项目,自搬移代码写错了地址偏移,结果 Bootloader 把自己覆盖了,系统直接死机。

阶段三:内核加载与启动

Bootloader 把内核镜像加载到 DDR 指定地址后,设置好启动参数(比如设备树地址、命令行参数),然后跳过去。内核开始解压、初始化子系统、加载驱动,最后挂载根文件系统。

这一步最常见的坑是设备树不匹配。内核根据设备树知道有哪些硬件,如果设备树和实际硬件对不上,驱动加载就会失败。我曾经因为设备树里 GPIO 编号写错,折腾了两天才找到原因。

启动模式:NOR / NAND / eMMC / SD 卡

不同的存储介质,启动方式差别很大。我整理了一张对比表,方便你快速查阅。

存储介质 接口类型 读取方式 常见 Bootloader 典型场景
NOR Flash 并行/SPI XIP(原地执行) U-Boot SPL 小容量、快速启动
NAND Flash 并行 需加载到 RAM U-Boot + SPL 大容量、成本敏感
eMMC MMC/SDIO 块设备读取 U-Boot + 分区表 手机、平板、工业控制
SD 卡 SPI/SDIO 块设备读取 U-Boot + MBR/GPT 开发调试、单板计算机

NOR Flash 启动

NOR Flash 支持XIP(eXecute In Place),也就是说 CPU 可以直接在 Flash 里执行代码,不用先复制到 RAM。启动速度最快,但容量小、价格贵。

我记得有个客户的产品要求上电后 500ms 内出画面,用的就是 NOR Flash + 精简 Bootloader。调试的时候发现,NOR Flash 的读取时序如果配错了,CPU 取指令会读到乱码,系统直接跑飞。

调试技巧:NOR Flash 启动失败时,先用逻辑分析仪抓一下片选信号和读信号,确认 CPU 是否真的在访问 Flash。很多时候是时序不匹配,不是代码问题。

NAND Flash 启动

NAND Flash 不能 XIP,因为它的读取方式是按页读取,而且有坏块问题。所以 Bootloader 必须先把一小段代码(通常叫 SPL,Secondary Program Loader)加载到内部 SRAM 里,再由 SPL 把完整的 Bootloader 加载到 DDR。

NAND 启动的坑主要在坏块管理和ECC 校验。我曾经遇到过一批 NAND Flash,出厂就有坏块,Bootloader 刚好写在坏块上,结果 10% 的设备起不来。后来我们在 SPL 里加了坏块跳过逻辑才解决。

eMMC 启动

eMMC 内部集成了 Flash 控制器和坏块管理,对软件来说就是一个块设备。启动时,芯片内部 ROM 会从 eMMC 的特定分区(Boot Partition)读取代码。

eMMC 启动的常见问题是分区配置错误。我调试过一个项目,eMMC 的 Boot Partition 大小没配够,Bootloader 写不进去,系统一直卡在 ROM 阶段。用 mmc partconf 命令重新配置分区后才解决。

SD 卡启动

SD 卡启动和 eMMC 类似,但多了个初始化时序的问题。SD 卡上电后需要先发 CMD0 让它进入 SPI 模式或 SD 模式,然后发 CMD8 检测电压范围。如果时序不对,卡就不响应。

我习惯在 Bootloader 里加一段 SD 卡初始化日志,打印出 CID 和 CSD 寄存器内容。这样能快速确认卡是否被正确识别。

启动流程中的关键检查点

根据我的经验,启动失败 80% 都出在下面这几个地方。你可以把它们当成排查清单。

  1. 复位向量是否被正确映射?——用调试器读一下 0x00000000 或 0xFFFF0000 的内容。
  2. DDR 初始化是否成功?——写一个已知值再读回来,确认读写一致。
  3. Bootloader 是否被正确加载?——检查存储介质的读取时序和地址映射。
  4. 内核镜像是否完整?——校验 CRC 或 SHA 哈希值。
  5. 设备树是否匹配硬件?——对比设备树里的寄存器地址和芯片手册。

我的经验:启动调试最忌讳一上来就怀疑内核。先确认硬件初始化没问题,再查 Bootloader,最后才看内核。从底层往上查,效率最高。

嗯,这一章的内容就到这里。启动流程看起来复杂,其实拆开来看就是几个固定步骤。你只要把每个阶段的输入输出搞清楚,排查问题就有方向了。