启动流程概览:从芯片上电到操作系统启动的完整链路

芯片上电那一刻,到底发生了什么?

很多人觉得启动就是「按一下电源键,然后屏幕亮了」。但在嵌入式世界里,这背后是一套精密的分级接力。我做了十几年底层固件,每次看到示波器上复位信号拉高、PC指针跳转到固定地址的那一刻,还是觉得挺有意思的——一个死气沉沉的芯片,就这样活过来了。

这一章,我们把整条链路拆开来看。从晶振起振,到操作系统开始调度第一个进程,中间到底经历了哪些阶段。说白了,就是回答一个问题:CPU 是怎么从「什么都不会」变成「什么都能干」的?

启动链路的五个核心阶段

我把整个启动过程分成五个阶段。你想想看,这就像接力赛——每一棒只干自己该干的事,干完了就交给下一棒。

阶段 执行者 主要任务 典型时间
阶段一:硬件初始化 硬件逻辑 + BootROM 时钟稳定、电源就绪、复位释放 微秒级
阶段二:BootROM 执行 片上 ROM 固件 基础外设初始化、加载下一级启动代码 毫秒级
阶段三:SPL/预加载器 SRAM 中的小代码 初始化 DDR、加载完整 U-Boot 数十毫秒
阶段四:U-Boot 主程序 DDR 中的 U-Boot 板级初始化、加载内核 百毫秒级
阶段五:操作系统启动 Linux 内核 驱动加载、文件系统挂载、init 进程 秒级

嗯,这里要注意——不同芯片的划分可能略有差异。比如有些 SoC 把阶段二和阶段三合并了,有些则拆得更细。但核心逻辑不变:每一级都比上一级更复杂,也更有「智能」。

阶段一:硬件初始化——芯片的「本能反应」

上电瞬间,芯片内部发生了什么?

我习惯把这段时间叫做「混沌期」。电源还没稳定,时钟还在振荡,复位信号还拉着低电平。这时候芯片什么都做不了,只能等。

  • 电源就绪:PMIC(电源管理芯片)按顺序输出各路电压。核心电压先上,IO 电压后上。顺序错了,芯片可能锁死。
  • 时钟稳定:晶振起振需要时间。PLL 锁定也需要时间。我记得有一次调试,板子死活起不来,最后发现是晶振负载电容配错了,起振时间长了 10 倍。
  • 复位释放:所有条件满足后,复位信号拉高。CPU 从复位向量地址取第一条指令。

关键点:复位向量地址是芯片出厂时硬编码的。对 ARM Cortex-A 系列来说,通常是 0x00000000 或 0xFFFF0000。对某些 RISC-V 芯片,可能是 0x80000000。这个地址指向的就是 BootROM。

阶段二:BootROM 执行——芯片的「出厂设置」

BootROM 是芯片上电后执行的第一段软件。它固化在芯片内部,你改不了。我刚开始做这行的时候,总觉得这玩意儿很神秘——后来发现,它其实就是一个非常精简的引导程序。

BootROM 的任务很明确:

  1. 初始化最基础的外设:通常是 SPI、NAND、SD/MMC、USB 或 UART。注意,它不会初始化 DDR——DDR 初始化需要复杂的时序训练,BootROM 那点空间放不下。
  2. 检测启动介质:根据 boot pin 或 OTP 配置,决定从哪个设备加载下一级代码。比如从 SD 卡启动,就从 SD 卡读取固定偏移处的数据。
  3. 加载并校验:把下一级代码读到芯片内部的 SRAM 中。SRAM 很小,通常只有几十 KB 到几百 KB。所以下一级代码也必须很小。
  4. 跳转执行:校验通过后,跳转到 SRAM 中的入口地址。

个人经验:我曾经在一个项目里遇到 BootROM 加载失败的问题。查了两天,最后发现是 SD 卡的分区表格式不对。BootROM 只认裸设备偏移,不认文件系统。你把它格式化成 FAT32,它反而找不到启动代码了。

阶段三:SPL/预加载器——承上启下的「小个子」

SPL(Secondary Program Loader)是 U-Boot 体系里的概念。有些芯片叫它 MLO、IBL 或者 pre-loader。名字不同,干的活一样。

为什么需要 SPL?因为 BootROM 的 SRAM 太小了,装不下完整的 U-Boot。SPL 就是那个「先挤进来,然后把门撑开」的角色。

SPL 的核心工作:

  • 初始化 DDR 控制器:这是最关键的步骤。DDR 初始化涉及时序参数、阻抗匹配、ZQ 校准。参数错了,系统直接挂掉。
  • 加载完整 U-Boot:从启动介质(SD 卡、eMMC、NAND Flash)把 U-Boot 主程序读到 DDR 中。
  • 可选的校验和压缩解压:有些方案会对 U-Boot 进行签名校验或 LZMA 压缩,SPL 负责解压和验证。

避坑指南:我曾经在一个项目里把 DDR 时序参数配错了。SPL 加载 U-Boot 时一切正常,但 U-Boot 一运行就随机崩溃。查了整整一周,最后用示波器量 DQS 信号才发现——时序偏差了 0.3ns。0.3ns,就这么点,系统就不稳定。

阶段四:U-Boot 主程序——真正的「系统管家」

U-Boot 主程序运行在 DDR 中,空间充裕了,能干的事也多起来。它不再像 BootROM 和 SPL 那样「缩手缩脚」。

U-Boot 的主要职责:

  1. 板级初始化:初始化网卡、USB、存储控制器、显示控制器等。这些外设的驱动代码都在 U-Boot 里。
  2. 环境变量加载:从存储介质读取 bootargs、bootcmd 等环境变量。这些变量决定了内核怎么启动。
  3. 内核加载与启动:从文件系统(FAT、ext4、UBIFS)或网络(TFTP)读取内核镜像,解析设备树,然后跳转到内核入口。

这里有个细节——U-Boot 启动内核时,会做一件很重要的事:关闭 MMU 和 Cache。因为内核启动时有自己的页表管理方式,U-Boot 必须把硬件状态恢复到「干净」的状态。

核心命令示例:

# 从 SD 卡加载内核到内存地址 0x82000000
fatload mmc 0:1 0x82000000 zImage

# 加载设备树
fatload mmc 0:1 0x83000000 imx6ull.dtb

# 设置启动参数
setenv bootargs console=ttymxc0,115200 root=/dev/mmcblk0p2 rootwait

# 启动内核
bootz 0x82000000 - 0x83000000

阶段五:操作系统启动——从「裸机」到「系统」

U-Boot 跳转到内核入口后,它的使命就完成了。内核接手后,会做以下几件事:

  • 解压自身:如果内核是压缩的(zImage),先自解压。
  • 初始化内核核心子系统:调度器、内存管理、中断系统。
  • 设备驱动加载:根据设备树,逐个初始化外设驱动。
  • 挂载根文件系统:从指定的存储设备挂载根文件系统。
  • 启动 init 进程:通常是 /sbin/init,然后由 init 启动所有用户态服务。

到这一步,整个启动链路就走完了。从芯片上电到用户看到登录提示符,中间经历了五级接力。

BootROM 与 U-Boot 的分工协作

很多人搞不清楚 BootROM 和 U-Boot 到底谁该干什么。我打个比方:

  • BootROM 是「保安」:它只认固定的流程,不会变通。它的代码是只读的,你改不了。它的任务就是确保系统能安全地启动起来。
  • U-Boot 是「管家」:它灵活、可配置、可扩展。你可以通过环境变量改变启动方式,可以通过命令交互调试,可以支持多种启动介质。

它们的分工边界很清晰:

维度 BootROM U-Boot
存储位置 芯片内部 ROM(只读) 外部 Flash(可读写)
可修改性 不可修改(出厂固化) 可修改、可升级
运行环境 SRAM(极小,通常 < 256KB) DDR(大,通常 > 64MB)
功能复杂度 极简(仅加载下一级) 丰富(网络、文件系统、命令行)
安全机制 基础签名校验 完整的安全启动链

我个人习惯把 BootROM 看作「硬件的一部分」。它虽然是一段软件,但它的行为逻辑和硬件一样——固定、可靠、不可变。而 U-Boot 才是真正的「软件」,你可以随意定制。

一个常见的误解:很多人以为 BootROM 里跑的是 U-Boot。不是的。BootROM 是芯片厂商写的,U-Boot 是板级厂商或开发者写的。它们之间通过「启动协议」对接——BootROM 把控制权交给 U-Boot 的 SPL,SPL 再加载完整的 U-Boot。

整条链路的可视化

下面这张图把整个启动流程串起来了。我建议你多看几遍,把每个阶段的「输入-处理-输出」理清楚。

嵌入式系统启动完整链路 阶段一:硬件初始化 电源就绪 · 时钟稳定 · 复位释放 阶段二:BootROM 检测介质 · 加载 SPL 到 SRAM 阶段三:SPL 初始化 DDR · 加载 U-Boot 阶段四:U-Boot 板级初始化 · 加载内核 阶段五:操作系统 驱动加载 · 文件系统 · init 关键说明: • 阶段一由硬件自动完成,无软件参与 • 阶段二(BootROM)固化在芯片内部,不可修改;阶段三/四(SPL + U-Boot)存储在外部 Flash,可升级 存储介质分布: • BootROM → 芯片内部 ROM(只读,出厂固化) • SPL → 外部 Flash 固定偏移(如 SD 卡第 1~512 扇区) • U-Boot → 外部 Flash 固定偏移(如 SD 卡第 512~2048 扇区)

几个需要记住的关键点

  • BootROM 是起点,不是终点:它只负责把下一级代码拉起来,然后就退出了。你没法在 BootROM 里加功能。
  • SPL 是瓶颈:它必须在 SRAM 里完成 DDR 初始化。SRAM 大小决定了 SPL 的复杂度上限。我见过有些芯片的 SRAM 只有 32KB,SPL 开发者得精打细算到每个字节。
  • U-Boot 是灵活性所在:几乎所有板级定制都在 U-Boot 里完成。换 Flash 型号、改 DDR 参数、加网络协议栈——都在 U-Boot 层面改。
  • 安全启动链:从 BootROM 到 U-Boot 到内核,每一级都要校验下一级的签名。只要有一级被篡改,系统就拒绝启动。这是防篡改的核心机制。

一个血的教训:我曾经在一个量产项目里,为了省成本换了颗便宜的 Flash 芯片。结果 BootROM 读 SPL 时总是超时——因为新 Flash 的读速度比原来的慢了一倍。BootROM 的读超时阈值是固定的,改不了。最后只能换回原来的 Flash。所以,换存储介质前,一定要确认 BootROM 的时序兼容性。