1. 嵌入式系统启动流程全景:从按下电源到用户界面的完整旅程
说实话,做了十几年嵌入式开发,我见过太多工程师只盯着自己那一亩三分地——搞bootloader的不知道根文件系统怎么挂载,写驱动的没看过内核怎么解压。结果呢?出了问题就抓瞎。
这一章,咱们把镜头拉远,看看整个启动流程的全貌。你想想看,从你按下电源键到屏幕上出现用户界面,这短短几秒里,芯片到底经历了什么?
1.1 启动流程的五个阶段
我把整个启动过程拆成五个阶段,每个阶段都有明确的分工。我个人习惯用这张图来跟团队新人讲解,一目了然。
1.2 第一阶段:ROM代码(BootROM)
芯片上电后,CPU会从一个固定地址开始执行。这个地址指向芯片内部固化的ROM代码,厂家出厂就写死了,你改不了。
ROM代码干了什么?
- 初始化最基本的硬件:关闭看门狗、设置系统时钟、初始化CPU缓存
- 初始化DDR控制器(有些芯片这一步留给SPL做)
- 根据启动引脚的电平状态,决定从哪个设备加载下一级代码
- 从NAND Flash、eMMC、SD卡或SPI NOR Flash读取SPL到内部SRAM
ROM代码的大小通常只有4KB~32KB,受限于芯片内部SRAM的大小。所以它只能做最基础的事情,然后赶紧把接力棒交出去。
1.3 第二阶段:SPL/MLO(二级引导器)
SPL(Secondary Program Loader)也叫MLO(MMC Loader),是ROM代码加载到SRAM里的小程序。它存在的意义很简单——ROM代码太小了,干不了重活。
SPL的核心任务:
- 完成DDR的完整初始化(如果ROM没做的话)
- 从存储设备加载U-Boot到DDR
- 跳转到U-Boot的入口地址
嗯,这里要注意:SPL本身也受限于SRAM大小,通常只有64KB~256KB。所以它必须精简,不能带太多功能。
1.4 第三阶段:U-Boot(主引导加载程序)
U-Boot到了DDR里,就像鱼入了水——空间大了,能干的事多了。它才是真正意义上的bootloader。
U-Boot的典型工作流程:
| 步骤 | 动作 | 说明 |
|---|---|---|
| 1 | 初始化串口、网卡、存储控制器 | 让你能看到打印信息,能通过网络下载文件 |
| 2 | 读取环境变量 | bootargs、bootcmd等关键参数 |
| 3 | 加载内核镜像到DDR | 从Flash、网络或SD卡读取zImage/Image |
| 4 | 加载设备树(DTB) | 告诉内核硬件布局 |
| 5 | 设置启动参数并跳转到内核 | 通过寄存器传递bootargs等 |
你想想看,U-Boot其实就是一个微型操作系统——它有命令行、文件系统支持、网络协议栈。我经常在U-Boot里用tftp下载内核调试,比反复烧写Flash快多了。
1.5 第四阶段:Linux内核启动
U-Boot跳转到内核入口后,内核开始接管系统。这个过程分两步:
第一步:解压内核
大多数嵌入式内核是压缩存储的(zImage或Image.gz)。内核开头有一段自解压代码,先把自身解压到DDR的另一个位置,然后跳过去执行。
// 内核启动时的关键打印信息(从串口看到的)
Uncompressing Linux... done, booting the kernel.
[ 0.000000] Booting Linux on physical CPU 0x0
[ 0.000000] Linux version 5.10.120 (builder@buildserver) (gcc 9.3.0)
[ 0.000000] CPU: ARMv7 Processor [410fc075] revision 5 (ARMv7)
[ 0.000000] Memory: 512MB
第二步:内核初始化
- 设置页表、开启MMU
- 初始化中断控制器、定时器
- 遍历设备树,匹配并初始化所有驱动
- 创建内核线程(kthreadd、kswapd等)
- 挂载根文件系统(这是关键一步,后面章节会细讲)
- 执行根文件系统里的init程序
1.6 第五阶段:根文件系统与用户界面
内核执行init程序(通常是/sbin/init),init进程的PID永远是1。它是所有用户进程的祖先。
init进程启动后:
- 读取配置文件(/etc/inittab或使用systemd的单元文件)
- 启动系统服务:网络、日志、SSH、看门狗等
- 启动显示服务器(如Qt的QWS、Wayland/Weston)
- 启动应用程序——用户界面终于出现了
说白了,从按下电源到看到界面,就是一场接力赛。每一棒都不能掉,每一棒的时间都要控制好。
1.7 各阶段耗时分布
给你一个典型数据,心里有个数:
| 阶段 | 典型耗时 | 优化空间 |
|---|---|---|
| ROM代码 | 50~200ms | 几乎无(芯片厂家固化的) |
| SPL | 100~500ms | 减少打印、优化存储读取 |
| U-Boot | 300ms~2s | 裁剪驱动、去掉延时、使用快速存储 |
| 内核解压+初始化 | 500ms~3s | 内核裁剪、使用LZ4压缩、设备树精简 |
| 根文件系统+应用 | 1~8s | 使用initramfs、并行启动服务、延迟加载 |
看到没?根文件系统这块反而是最耗时的。这也是为什么我把这门课叫做《Bootloader分区与根文件系统协同实战》——这两者配合好了,启动速度能快一大截。