3、嵌入式Linux启动流程概览:从上电到Shell提示符的完整流程
嵌入式Linux的启动,说白了就是一段从硬件复位到用户交互的旅程。我刚开始接触这块时,总觉得这个过程很神秘——按下电源键,几秒钟后Shell就出来了,中间到底发生了什么?
今天我们就来拆解这条完整的启动链路。你想想看,一个嵌入式设备从冷启动到出现$提示符,中间要经历几个关键阶段。每个阶段都有它的使命,也有它的坑。
3.1 启动流程全景图
先看一张整体流程图,把整个启动过程串起来。这张图我画了很多次,每次给团队新人培训都会拿出来讲。
这张图把启动过程分成了6个阶段。嗯,这里要注意——不是所有平台都严格走这6步,但大体框架是一样的。我个人习惯把前三个阶段统称为"Bootloader阶段",后三个阶段叫"内核启动阶段"。
3.2 阶段一:上电复位与BootROM
按下电源键的那一刻,CPU的复位引脚被拉高,程序计数器(PC)被强制指向一个固定地址。这个地址里放的是什么?是芯片厂商固化在ROM里的启动代码,我们叫它BootROM。
BootROM的核心任务:
- 初始化最基本的硬件:关闭看门狗、设置系统时钟、初始化缓存
- 检测启动介质:从SD卡、eMMC、NAND Flash、SPI Flash等介质中读取下一级代码
- 加载SPL(Secondary Program Loader)到内部SRAM
我在项目中遇到过一个问题:某款芯片的BootROM只支持从特定偏移地址读取SPL。当时我们没仔细看手册,把SPL放错了位置,结果板子死活起不来。折腾了两天才发现是偏移地址搞错了。
3.3 阶段二:SPL/MLO——二级引导程序
SPL,全称Secondary Program Loader。有些平台叫它MLO(比如TI的AM335x系列)。为什么需要这层?因为BootROM太小了,通常只有几十KB,干不了复杂的事。
SPL的主要工作就两件:
- 初始化DDR内存——这是最关键的一步。没有DDR,你连U-Boot完整版都跑不起来。
- 从存储介质加载U-Boot完整版到DDR——然后跳转过去执行。
避坑指南:我曾经在移植U-Boot到新板子时,SPL阶段的DDR初始化参数配错了。结果U-Boot加载到DDR后,一运行就死机。后来用JTAG单步调试才发现,DDR的时序参数跟实际硬件不匹配。所以,DDR初始化参数一定要跟硬件工程师确认,别自己瞎猜。
3.4 阶段三:U-Boot完整版
U-Boot完整版加载到DDR后,才真正进入"Bootloader的完全体"。这个阶段的U-Boot功能就丰富多了:
| 功能模块 | 说明 | 我的经验 |
|---|---|---|
| 串口初始化 | 初始化调试串口,输出启动信息 | 波特率别设错,115200最常见 |
| 网络初始化 | 初始化以太网控制器,支持TFTP下载 | 调试阶段强烈建议用TFTP,省去反复烧写 |
| 设备树加载 | 从存储介质读取设备树文件(.dtb) | 设备树地址要跟内核约定好 |
| 内核加载 | 读取内核镜像(zImage/Image)到内存 | 注意内核压缩格式,别搞混了 |
| 启动参数传递 | 设置bootargs,传给内核 | console=ttyS0,115200 是标配 |
U-Boot执行到最后,会调用bootm或bootz命令,把控制权交给内核。这时候U-Boot的使命就完成了。
3.5 阶段四:Linux内核启动
内核被U-Boot加载到内存后,开始执行。内核启动又可以分为几个子阶段:
内核启动子阶段:
- 解压阶段——如果内核是压缩的(zImage),先自解压。你会看到屏幕上出现"Uncompressing Linux..."
- 架构初始化——设置页表、初始化中断控制器、初始化定时器
- 设备驱动初始化——根据设备树,逐个初始化注册的驱动
- 挂载根文件系统——根据bootargs里的root=参数,挂载根文件系统
- 启动init进程——执行/sbin/init,进入用户空间
内核启动过程中,你会看到一串串的打印信息。我建议你仔细看这些信息——它们是诊断问题的第一手资料。比如某个驱动初始化失败,内核会打印"failed to probe"之类的信息。
3.6 阶段五:根文件系统与init进程
内核启动init进程后,就进入了用户空间。init进程是用户空间的第一个进程,PID为1。它负责:
- 读取
/etc/inittab(BusyBox系统)或/etc/init/目录下的配置文件 - 启动系统服务:网络服务、telnet/SSH、syslog等
- 启动getty,在串口上显示登录提示符
根文件系统的类型有很多种:
| 文件系统类型 | 特点 | 适用场景 |
|---|---|---|
| initramfs | 集成在内核中,内存中运行 | 调试阶段、临时系统 |
| JFFS2 / UBIFS | 支持NAND Flash,有磨损平衡 | 产品量产,NAND Flash |
| ext4 | 性能好,支持大文件 | eMMC、SD卡 |
| NFS | 网络挂载,开发调试方便 | 开发阶段,不用反复烧写 |
注意:根文件系统挂载失败是初学者最常见的坑。内核打印"VFS: Unable to mount root fs"时,别慌。先检查bootargs里的root=参数是否正确,再看文件系统镜像是否损坏。我遇到过最离谱的一次,是文件系统烧写时校验和算错了,导致内核读出来的全是乱码。
3.7 阶段六:Shell提示符
当init进程启动getty后,串口上就会出现登录提示符:
Welcome to Buildroot
buildroot login: root
Password:
#
如果你用的是BusyBox,默认可能不需要密码,直接回车就进去了。看到#提示符,说明整个启动流程走完了。
从按下电源到出现#,整个过程通常需要多长时间?我测过几款常见的开发板:
| 开发板 | CPU | 启动时间(典型值) |
|---|---|---|
| Raspberry Pi 3 | BCM2837 (4核A53) | 约8秒 |
| BeagleBone Black | AM3358 (单核A8) | 约12秒 |
| i.MX6ULL | 单核A7 | 约6秒 |
| STM32MP157 | 双核A7 | 约5秒 |
当然,这些时间跟你的内核大小、文件系统复杂度、存储介质速度都有关系。优化启动时间是个专门的课题,后面章节我们会详细讲。
3.8 启动流程中的关键决策点
整个启动流程中,有几个关键决策点决定了系统能不能正常启动:
- BootROM能否找到SPL?——取决于启动介质和偏移地址是否正确
- SPL能否正确初始化DDR?——DDR参数配错,后面全白搭
- U-Boot能否找到内核和设备树?——存储分区、文件名、加载地址都要对
- 内核能否挂载根文件系统?——文件系统类型、设备节点、驱动都要匹配
这四个决策点,任何一个出问题,系统都起不来。我建议你在调试新板子时,按这个顺序逐个排查。别一上来就怀疑内核,先看看BootROM有没有跑起来。
调试小技巧:在U-Boot阶段,可以用printenv查看当前的环境变量,用bdinfo查看板级信息,用mmc info查看存储设备。这些命令能帮你快速定位问题。我每次移植新板子,第一件事就是确保串口能打印U-Boot信息——只要看到U-Boot的logo,后面的事就好办了。
好了,嵌入式Linux的完整启动流程就讲到这里。从BootROM到Shell提示符,每一步都有它的意义。理解了整体流程,后面我们讲U-Boot移植时,你就能知道每个配置项到底影响哪个阶段了。