1. 启动流程概览:从按下电源键到内核启动
各位同学,今天咱们来聊聊嵌入式系统上电后那点事儿。很多人觉得按下电源键,屏幕亮了,系统就起来了——其实背后是一套精密的接力赛。我做了十几年嵌入式,见过不少同事在启动阶段栽跟头,说白了就是没搞懂这个链路里每个角色到底在干嘛。
先给你一张全景图,把整个启动流程串起来:
1.1 BootROM:芯片出厂就有的"第一行代码"
按下电源键的瞬间,CPU复位,PC指针直接跳到一个固定地址——那就是BootROM。BootROM是芯片出厂时固化在内部ROM里的代码,你改不了它,也删不掉它。它的任务很简单:初始化最基本的硬件(比如时钟、片内SRAM),然后从预设的启动介质(NAND、eMMC、SD卡、UART等)读取下一级代码。
关键点:BootROM的大小通常只有几十KB,它没有能力初始化DDR,所以下一级代码必须能在片内SRAM里跑。
我记得有一次调试一块新板子,死活起不来。查了半天,发现是BootROM配置的启动引脚电平不对,它一直在等UART下载,根本没去读eMMC。这种问题最坑人——你连第一行代码都没跑对,后面全是白费功夫。
1.2 SPL:从"小"到"大"的桥梁
SPL(Secondary Program Loader)是很多SoC引入的中间层。为什么需要它?因为BootROM太小了,干不了复杂的事。SPL的任务是:初始化DDR控制器,把DDR跑起来,然后把完整的U-Boot从存储介质加载到DDR里。
你想想看,BootROM只有几十KB的SRAM可用,而U-Boot动不动就几百KB甚至上MB。没有SPL这个"二传手",U-Boot根本放不下。SPL本身也很小,一般几十KB,刚好塞进片内SRAM。
个人经验:我建议你在调试SPL时,多用串口打印。SPL阶段没有复杂的调试手段,一个简单的putchar()就能帮你定位90%的问题。我曾经靠串口输出的几个"OK"字符,找到了DDR时序配置的错误。
1.3 U-Boot:嵌入式世界的"BIOS"
U-Boot是嵌入式Linux最常用的引导加载程序。它比SPL复杂得多,提供了完整的命令行界面、网络支持、文件系统驱动、设备树支持等。U-Boot的主要工作:
- 初始化更复杂的硬件(网卡、USB、显示控制器等)
- 从存储介质读取内核镜像和设备树文件
- 解析设备树,修改内核启动参数
- 跳转到内核入口点,把控制权交给内核
说白了,U-Boot就是内核的"保姆"——它把硬件环境准备好,把内核镜像放到内存里,然后喊一声"内核,该你上了"。
避坑指南:我曾经遇到过U-Boot环境变量被意外擦除,导致启动参数丢失,内核找不到根文件系统。从那以后,我习惯在量产前把关键环境变量写到代码里,而不是依赖存储介质上的env分区。
1.4 Linux内核:真正的"操作系统"
当U-Boot跳转到内核入口时,内核开始接管系统。内核的启动过程大致分为两个阶段:
- 架构相关初始化:设置页表、初始化中断控制器、时钟系统等。这部分代码是汇编写的,跟具体CPU架构绑定。
- 架构无关初始化:start_kernel()函数开始执行,初始化进程调度器、内存管理、文件系统、网络协议栈等核心子系统。
内核启动的最后一步是挂载根文件系统,然后启动第一个用户空间进程(通常是init或systemd)。从这一刻起,内核退居幕后,用户空间开始唱主角。
注意:内核启动过程中,设备树(Device Tree)扮演了关键角色。它告诉内核"我有哪些硬件、它们怎么连接、需要什么驱动"。这部分内容我们会在后续章节详细展开。
1.5 根文件系统:用户空间的"家"
根文件系统(RootFS)是内核启动后挂载的第一个文件系统。它包含了用户空间所需的一切:init程序、库文件、配置文件、设备节点、应用程序等。没有根文件系统,内核启动后就会panic——因为它找不到第一个进程。
常见的根文件系统类型:
| 类型 | 特点 | 适用场景 |
|---|---|---|
| initramfs | 内嵌在内核镜像中,内存中解压 | 早期启动、调试、临时系统 |
| ext4 | 成熟稳定,支持日志 | eMMC、SD卡等块设备 |
| squashfs | 只读、高压缩比 | 嵌入式只读系统 |
| UBIFS | 针对NAND Flash优化 | NAND Flash设备 |
我个人习惯在开发阶段用initramfs,改完内核和驱动后重新打包就行,不用反复烧写存储介质。等产品定型了,再换成squashfs+overlayfs的组合,兼顾只读安全和可写需求。
1.6 各阶段的分工总结
把整个链路串起来,每个角色的职责其实很清晰:
| 阶段 | 代码位置 | 主要职责 | 典型大小 |
|---|---|---|---|
| BootROM | 芯片内部ROM | 初始化时钟、SRAM,加载SPL | 16-64 KB |
| SPL | 片内SRAM | 初始化DDR,加载U-Boot | 32-128 KB |
| U-Boot | DDR | 初始化外设,加载内核和设备树 | 200-800 KB |
| Kernel | DDR | 初始化系统核心,挂载RootFS | 2-10 MB |
| RootFS | 存储介质/内存 | 提供用户空间运行环境 | 4-256 MB |
嗯,这里要注意一点:不同SoC的启动流程可能有细微差别。比如有些芯片把SPL和U-Boot合并了,有些用ATF(ARM Trusted Firmware)代替了SPL。但核心思想是一样的——逐级加载,层层递进。
我曾经在一个项目里遇到过启动时间过长的问题,逐级排查后发现是U-Boot在等待网络超时。加了个"bootdelay=0"的环境变量,启动时间从8秒降到了2秒。你看,有时候问题就藏在这些细节里。