3、嵌入式Linux启动流程概览:从上电到Shell提示符的完整流程

嵌入式Linux的启动,说白了就是一段从硬件复位到用户交互的旅程。我刚开始接触这块时,总觉得这个过程很神秘——按下电源键,几秒钟后Shell就出来了,中间到底发生了什么?

今天我们就来拆解这条完整的启动链路。你想想看,一个嵌入式设备从冷启动到出现$提示符,中间要经历几个关键阶段。每个阶段都有它的使命,也有它的坑。

3.1 启动流程全景图

先看一张整体流程图,把整个启动过程串起来。这张图我画了很多次,每次给团队新人培训都会拿出来讲。

嵌入式Linux启动流程全景图 嵌入式Linux启动流程全景图 ① 上电复位 BootROM / 固化代码 ② SPL / MLO 初始化DDR、时钟 ③ U-Boot 完整版 加载内核、设备树 ④ Linux Kernel 解压、初始化 ⑤ 根文件系统 挂载、init进程 ⑥ Shell提示符 用户交互 关键说明: • 阶段①~③属于Bootloader阶段,负责硬件初始化和加载内核 • 阶段④~⑤属于内核启动阶段,包括内核解压、驱动加载、文件系统挂载 • 阶段⑥是用户空间初始化完成,启动Shell或GUI • 整个流程从按下电源到Shell提示符,通常需要3~30秒(取决于硬件性能)

这张图把启动过程分成了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的主要工作就两件:

  1. 初始化DDR内存——这是最关键的一步。没有DDR,你连U-Boot完整版都跑不起来。
  2. 从存储介质加载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 启动流程中的关键决策点

整个启动流程中,有几个关键决策点决定了系统能不能正常启动:

  1. BootROM能否找到SPL?——取决于启动介质和偏移地址是否正确
  2. SPL能否正确初始化DDR?——DDR参数配错,后面全白搭
  3. U-Boot能否找到内核和设备树?——存储分区、文件名、加载地址都要对
  4. 内核能否挂载根文件系统?——文件系统类型、设备节点、驱动都要匹配

这四个决策点,任何一个出问题,系统都起不来。我建议你在调试新板子时,按这个顺序逐个排查。别一上来就怀疑内核,先看看BootROM有没有跑起来。

调试小技巧:在U-Boot阶段,可以用printenv查看当前的环境变量,用bdinfo查看板级信息,用mmc info查看存储设备。这些命令能帮你快速定位问题。我每次移植新板子,第一件事就是确保串口能打印U-Boot信息——只要看到U-Boot的logo,后面的事就好办了。

好了,嵌入式Linux的完整启动流程就讲到这里。从BootROM到Shell提示符,每一步都有它的意义。理解了整体流程,后面我们讲U-Boot移植时,你就能知道每个配置项到底影响哪个阶段了。