2. 启动流程概览:从SoC上电到操作系统启动的完整链路
各位同学,今天我们来聊聊整个启动流程的全貌。很多初学者一上来就盯着U-Boot的代码看,结果越看越晕。我个人习惯是,先搞清楚"从按下电源到系统跑起来,中间到底发生了什么",再去看细节。
说白了,嵌入式系统的启动就像一场接力赛。每个阶段都有自己的任务,跑完就把接力棒交给下一棒。我们来看看这根接力棒是怎么传递的。
2.1 完整启动链路:五棒接力
从SoC上电到操作系统启动,我把它拆成五个阶段。你想想看,每个阶段都有明确的输入和输出:
| 阶段 | 执行者 | 主要任务 | 输出 |
|---|---|---|---|
| 1. 上电复位 | 硬件逻辑 | 复位所有寄存器,从复位向量取指 | PC指向ROM代码入口 |
| 2. Boot ROM | 片上ROM固件 | 初始化时钟、检测启动介质、加载SPL | SPL被加载到SRAM |
| 3. SPL | SPL(U-Boot第一阶段) | 初始化DDR、加载U-Boot proper | U-Boot proper被加载到DDR |
| 4. U-Boot proper | U-Boot第二阶段 | 初始化外设、加载设备树、启动内核 | 内核被加载到DDR并跳转 |
| 5. 操作系统 | Linux内核 | 解压、初始化子系统、挂载根文件系统 | 用户空间启动 |
嗯,这里要注意:并不是所有芯片都有Boot ROM。有些低端MCU直接从片上Flash执行,跳过了Boot ROM阶段。但大部分应用处理器(比如i.MX、Rockchip、Allwinner)都有Boot ROM。
2.2 为什么需要两个阶段的U-Boot?
这个问题我经常被问到。为什么不能一个U-Boot搞定所有事情?
原因很简单:SRAM太小,DDR还没初始化。
SoC上电时,DDR控制器还没配置,DDR根本不能用。芯片内部只有一小块SRAM可用,通常只有几十KB到几百KB。而完整的U-Boot proper编译出来动辄几百KB甚至几MB,根本放不下。
所以,设计者想了个办法:
- SPL:精简到极致,只做两件事——初始化DDR,加载U-Boot proper到DDR。
- U-Boot proper:完整的引导加载程序,负责加载内核、设备树、ramdisk等。
核心原则:SPL是"最小可行引导程序",它的代码量必须能塞进SRAM。我见过一些项目为了省SRAM,SPL里连串口调试都砍掉了——结果出了问题根本没法查,得不偿失。
2.3 SPL与U-Boot proper的分工
我们来看一张流程图,把这两个阶段的关系理清楚:
经验之谈:我曾经在一个项目里,SPL编译出来刚好比SRAM大了2KB。当时想偷懒没优化,结果芯片死活起不来。后来发现Boot ROM加载SPL时会做大小校验,超了就拒绝执行。从那以后,我每次都会在链接脚本里加一个SECTION_SIZE检查。
2.4 SPL的"瘦身"哲学
SPL的代码量必须严格控制。我给大家列一下SPL通常包含什么、不包含什么:
| 模块 | SPL是否包含 | 原因 |
|---|---|---|
| DDR初始化 | ✅ 必须 | 没有DDR,后续代码无处安放 |
| 串口输出 | ✅ 强烈建议 | 调试救命用的,别省 |
| 时钟初始化 | ✅ 必须 | DDR需要特定时钟频率 |
| 存储驱动(MMC/NAND/SPI) | ✅ 必须 | 用来读取U-Boot proper |
| 文件系统支持 | ❌ 通常不包含 | 太占空间,用原始块读取即可 |
| 网络协议栈 | ❌ 不包含 | SPL不需要网络功能 |
| 设备树解析 | ❌ 不包含 | SPL用硬编码配置 |
| 命令行交互 | ❌ 不包含 | SPL没有shell |
避坑指南:我曾经见过一个团队,为了"功能完整",在SPL里加了FAT32文件系统支持。结果SPL膨胀到300KB,而芯片SRAM只有128KB。最后不得不重写,浪费了两周时间。记住:SPL不是U-Boot的缩水版,它是专门为SRAM环境设计的极简引导程序。
2.5 启动介质的选择与回退
Boot ROM怎么知道从哪个设备加载SPL?
大部分SoC的做法是:
- 读取硬件引脚电平(Boot Mode Pins)
- 根据引脚状态决定启动介质优先级
- 按优先级依次尝试,直到成功
举个例子,某款芯片的启动顺序可能是:
- SD卡(如果检测到)
- eMMC(如果检测到)
- SPI NOR Flash
- NAND Flash
- USB下载模式(如果以上都失败)
嗯,这里有个细节:Boot ROM通常会在每个介质上查找特定的"签名"或"头部信息"。比如i.MX系列要求SPL镜像前128字节包含Image Vector Table(IVT),里面记录了SPL的加载地址和入口点。如果找不到合法的IVT,Boot ROM就认为这个介质不可用,跳到下一个。
关键点:SPL的存放位置和格式是由芯片厂商定义的,不是U-Boot自己说了算。你在移植U-Boot到新板子时,第一件事就是去查芯片参考手册里的"Boot ROM"章节,搞清楚它期望的镜像格式。
2.6 从SPL到U-Boot proper的跳转
SPL把U-Boot proper加载到DDR之后,怎么跳过去?
流程大致是这样:
- SPL从存储介质读取U-Boot proper镜像到DDR的指定地址
- SPL关闭中断、清理缓存(如果需要)
- SPL设置好栈指针,跳转到U-Boot proper的入口地址
- U-Boot proper重新初始化部分硬件(比如串口、定时器)
- U-Boot proper开始执行它的启动流程
这里有个容易踩的坑:DDR的配置参数。SPL初始化DDR时用的参数,U-Boot proper必须完全兼容。如果SPL和U-Boot proper用了不同的DDR时序配置,轻则内存访问出错,重则芯片死机。
我的做法:把DDR配置参数放在一个单独的头文件里,SPL和U-Boot proper都包含它。这样改参数时不会出现两边不一致的情况。这个习惯帮我避免了好几次线上事故。
2.7 小结:记住这三句话
好了,这一章的内容就这些。我不喜欢长篇大论的总结,你只要记住三句话:
- 启动是接力赛:Boot ROM → SPL → U-Boot proper → Kernel,每一棒只做自己该做的事。
- SPL存在的唯一理由:SRAM太小,装不下完整的U-Boot。所以SPL必须瘦身,只做"初始化DDR + 加载U-Boot proper"两件事。
- 启动介质由硬件决定:Boot Mode引脚决定了从哪里加载SPL,镜像格式由芯片厂商定义,移植时先看手册。