2. 嵌入式Linux启动流程

嵌入式 Linux 设备从上电到出现可交互的 Shell 或应用界面,并不是“一步到位”的,而是由 Bootloader、Linux 内核、根文件系统 三个核心角色按严格顺序接力完成的。理解它们之间的依赖关系,是学习 Buildroot 构建根文件系统的前提:Buildroot 主要负责生成根文件系统,同时也可能生成内核、Bootloader 或它们的镜像打包形式,因此必须清楚每一环“交给下一环什么”。

核心结论:Bootloader 负责“把内核拉起来”,内核负责“把硬件和根文件系统挂起来”,根文件系统负责“提供用户空间和应用程序”。三者缺一不可,且启动顺序不可颠倒。

一、三者的职责与依赖关系

可以把整个启动过程理解为一条“信任链”和“控制权移交链”:

  • Bootloader(如 U-Boot):上电后最先运行。它初始化最基础的时钟、DDR、串口等,然后从 Flash、SD 卡、eMMC 或网络加载内核镜像(如 zImage、Image)和设备树(.dtb),最后把控制权交给内核。
  • Linux 内核:接管 CPU 后完成内存管理、中断、驱动、文件系统等初始化。内核启动的最后阶段,必须挂载一个根文件系统,并执行其中的 init 程序,才能进入用户空间。
  • 根文件系统(rootfs):提供 /bin、/sbin、/etc、/lib、/dev 等目录结构,以及 init 进程、Shell、动态库和应用程序。没有它,内核只能停在“Kernel panic - not syncing: No init found”之类的错误上。

三者关系可以用下面的信号链与数据流图表示:

嵌入式 Linux 启动接力:从 Bootloader 到根文件系统 Bootloader U-Boot / SPL 初始化 DDR、串口、存储 加载 zImage + dtb Linux 内核 zImage / Image 驱动、内存、调度初始化 挂载 rootfs,执行 init 根文件系统 rootfs / ext4 / squashfs /bin /etc /lib /dev init、Shell、应用程序 移交控制权 挂载并执行 启动参数传递: bootargs = console=ttyS0,115200 root=/dev/mmcblk0p2 rootfstype=ext4 rw rootwait 阶段一:裸机/固件 CPU 复位向量 → SPL → U-Boot 阶段二:内核空间 start_kernel → 驱动 → mount rootfs 阶段三:用户空间 init → 服务 → 应用/Shell

二、启动流程的关键阶段

以典型的 ARM 嵌入式平台为例,启动过程可以拆成三个大阶段:

  1. Bootloader 阶段:芯片上电后先执行片内 ROM 代码,加载 SPL(二级程序加载器)到 SRAM,再由 SPL 初始化 DDR 并加载完整的 U-Boot 到内存。U-Boot 启动后进入命令行或自动引导流程,读取环境变量 bootargs 和 bootcmd,把内核镜像和设备树搬到内存指定地址。
  2. 内核阶段:内核解压并自解压后,执行 start_kernel(),依次完成内存管理、中断控制器、时钟、串口、存储控制器等初始化。随后内核根据 bootargs 中的 root= 参数寻找根文件系统。若根文件系统在 eMMC 上,通常写成 root=/dev/mmcblk0p2;若使用 initramfs,则写成 root=/dev/ram0 或直接由内核内置。
  3. 用户空间阶段:内核挂载根文件系统后,执行 /sbin/init 或 /init。Buildroot 默认使用 BusyBox 的 init,它读取 /etc/inittab,启动 /etc/init.d/rcS 中的脚本,最终拉起 Shell 或应用程序。

下面是一段典型的 U-Boot 环境变量与内核启动参数示例:

setenv bootargs 'console=ttyS0,115200 root=/dev/mmcblk0p2 rootfstype=ext4 rw rootwait'
setenv bootcmd 'mmc dev 0; fatload mmc 0:1 0x80800000 zImage; fatload mmc 0:1 0x83000000 imx6ull.dtb; bootz 0x80800000 - 0x83000000'
saveenv
boot

内核启动日志中,与根文件系统相关的关键行通常如下:

[    0.000000] Kernel command line: console=ttyS0,115200 root=/dev/mmcblk0p2 rootfstype=ext4 rw rootwait
[    1.234567] mmcblk0: mmc0:0001 SD16G 14.6 GiB
[    1.345678] EXT4-fs (mmcblk0p2): mounted filesystem with ordered data mode
[    1.456789] VFS: Mounted root (ext4 filesystem) on device 179:2.
[    1.567890] Run /sbin/init as init process

三、典型场景:Buildroot 产物如何对应启动链

在 Buildroot 构建体系中,输出目录 output/images/ 下通常会出现以下文件,它们正好对应启动链的不同环节:

启动环节 Buildroot 产物 作用
Bootloader u-boot.bin、SPL 初始化硬件并加载内核
内核 zImage、Image、*.dtb 驱动硬件、挂载根文件系统
根文件系统 rootfs.ext4、rootfs.tar、rootfs.squashfs 提供 init、库、应用和配置
打包镜像 sdcard.img、*.img 把以上三者按分区烧录到存储介质

例如,当使用 SD 卡启动时,典型分区布局为:

分区1:FAT32,存放 zImage 和 imx6ull.dtb
分区2:ext4,存放 rootfs.ext4 解压后的根文件系统
Bootloader:写入 SD 卡偏移 0x400 或 0x8 处(依芯片而定)

四、常见误区与排障复盘

误区一:认为“内核启动成功就等于系统启动成功”。实际上,如果根文件系统挂载失败,内核会打印 Kernel panic - not syncing: VFS: Unable to mount root fs,系统仍然无法进入用户空间。
误区二:只改根文件系统,却忽略 bootargs 中的 root= 参数。根文件系统换了分区或文件系统类型,必须同步修改内核命令行,否则内核会去错误的位置寻找 rootfs。

排障时建议按以下顺序检查:

  • 串口是否有 U-Boot 输出?没有则检查 Bootloader 烧录位置和启动介质选择。
  • U-Boot 是否能加载内核和设备树?检查 fatload、mmc 命令和内存地址。
  • 内核是否打印 Kernel command line?确认 bootargs 是否被正确传递。
  • 是否出现 Mounted root?没有则检查分区号、文件系统类型和驱动是否编译进内核。
  • 是否执行 /sbin/init?没有则检查根文件系统中是否存在 init 程序及其动态库依赖。

理解 Bootloader、内核、根文件系统三者的接力关系后,再学习 Buildroot 就会更有方向:Buildroot 不仅生成 rootfs,还可以统一管理内核配置、Bootloader 配置和最终镜像打包,使整条启动链在构建阶段就被确定下来,减少“板子起不来却不知道哪一环出错”的排障成本。