1. 启动流程概览:从按下电源键到内核启动

各位同学,今天咱们来聊聊嵌入式系统上电后那点事儿。很多人觉得按下电源键,屏幕亮了,系统就起来了——其实背后是一套精密的接力赛。我做了十几年嵌入式,见过不少同事在启动阶段栽跟头,说白了就是没搞懂这个链路里每个角色到底在干嘛。

先给你一张全景图,把整个启动流程串起来:

嵌入式系统启动全链路 BootROM 芯片固化代码 SPL 二级引导程序 U-Boot 引导加载程序 Linux Kernel 操作系统内核 RootFS 根文件系统 Application 用户应用程序 初始化硬件 加载下一级引导 挂载根文件系统 启动用户空间 时间轴 → Stage 1 Stage 2 Stage 3 硬件初始化 DDR初始化 加载内核 加载SPL 加载U-Boot 启动内核 Stage 4: 内核初始化 Stage 5: 挂载RootFS

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跳转到内核入口时,内核开始接管系统。内核的启动过程大致分为两个阶段:

  1. 架构相关初始化:设置页表、初始化中断控制器、时钟系统等。这部分代码是汇编写的,跟具体CPU架构绑定。
  2. 架构无关初始化: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秒。你看,有时候问题就藏在这些细节里。