2. 嵌入式 Linux 启动流程

在嵌入式 Linux 系统中,从上电到出现可交互的 shell,并不是某一个程序独立完成的任务,而是 Bootloader、Kernel、Rootfs 三个角色按严格顺序接力协作的结果。理解这条链路,是后续用 BusyBox 定制根文件系统、决定“根文件系统放在哪里、怎么被挂载”的前提。很多启动失败问题,本质上不是内核坏了,而是三者之间的“交接约定”没有对齐。

一、核心原理:三阶段接力与职责边界

可以把整个启动过程理解为一次接力赛,每一棒只负责把系统推进到下一棒能够接手的程度:

  • Bootloader 阶段:芯片上电后先执行固化在 ROM 中的一级引导,再跳转到 Bootloader(如 U-Boot)。它负责最基础的时钟、DDR、串口初始化,然后从存储介质中把内核镜像和设备树加载到内存,最后把控制权交给内核。
  • Kernel 阶段:内核解压并自解压后开始运行,完成内存管理、中断、驱动模型、调度器等核心子系统的初始化,并根据 bootargs 中的 root= 参数去定位根文件系统。
  • Rootfs 阶段:内核挂载根文件系统后,执行其中的 /sbin/init(或 /init),由 init 进程拉起服务、挂载其余文件系统,最终启动登录 shell。对 BusyBox 系统而言,这个 init 通常就是 BusyBox 提供的 init 或 busybox init。

三者的关键交接点有两个:一是 Bootloader 向内核传递 启动参数(bootargs)和 机器/设备树信息;二是内核向用户空间传递 根文件系统位置。任何一处参数写错,都会表现为“内核起来了但找不到根文件系统”或“挂载成功但没有 init”。

嵌入式 Linux 启动接力:Bootloader → Kernel → Rootfs ① Bootloader 时钟 / DDR / 串口初始化 加载 zImage + dtb 到内存 设置 bootargs 跳转内核入口 ② Kernel 解压 / 自解压 驱动与子系统初始化 解析 root= 参数 挂载根文件系统 ③ Rootfs 执行 /sbin/init BusyBox init 解析 inittab 挂载 /proc /sys /dev 启动 shell / 应用 bootargs root= 常见存储介质与 Rootfs 加载方式 NOR / NAND Flash mtdblock + jffs2/ubifs eMMC / SD 卡 /dev/mmcblk0p2 ext4 NFS 网络 root=/dev/nfs 开发调试 Initramfs / RAMDisk 内存中解压,无需块设备

二、方法流程:从 bootargs 到 init 的完整链路

以 U-Boot + 内核 + BusyBox rootfs 的典型组合为例,启动参数通常这样设置:

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

内核启动后,会依次完成以下动作:

  1. 解析 root= 指定的设备节点,等待该块设备就绪(rootwait 常用于 eMMC/SD 卡)。
  2. 按 rootfstype= 指定的文件系统类型挂载根分区。
  3. 若挂载成功,执行 init= 指定的程序;未指定时按 /sbin/init、/etc/init、/bin/init、/bin/sh 顺序查找。
  4. BusyBox init 读取 /etc/inittab,按 sysinit、respawn、askfirst 等动作启动控制台和脚本。

如果使用 Initramfs,则内核在挂载真正的根文件系统之前,会先把内嵌的 cpio 归档解压到内存中的 rootfs,执行其中的 /init,由它完成驱动加载、分区检查后再 switch_root 到真实根分区。这也是很多量产设备“先内存启动、再切换到 eMMC”的常见做法。

三、案例场景:三种典型加载方式对比

加载方式 典型 bootargs 适用场景 注意点
块设备根文件系统 root=/dev/mmcblk0p2 rootfstype=ext4 量产设备、eMMC/SD 卡 分区号、文件系统类型必须与烧录一致
NFS 网络根文件系统 root=/dev/nfs nfsroot=192.168.1.10:/nfsroot 开发调试、快速迭代 需内核支持 NFS,网络与 IP 参数要正确
Initramfs / RAMDisk root=/dev/ram0 或内嵌 cpio 救援系统、两阶段启动 占用内存,需处理 switch_root

四、误区与复盘:启动失败的高频原因

误区一:内核启动成功就等于系统启动成功。 内核打印出 VFS: Cannot open root device 或 Kernel panic - not syncing: No init found 时,问题往往在 rootfs 侧,而不是内核本身。

  • root= 设备名不匹配:eMMC 在不同内核版本下可能是 /dev/mmcblk0p2 或 /dev/mmcblk1p2,建议用 root=PARTUUID= 或 root=UUID= 提高稳定性。
  • 缺少 init 或权限错误:BusyBox 生成的 /sbin/init 必须是可执行文件,且动态链接时 /lib 下的库要齐全,否则会报 No init found。
  • inittab 配置错误:BusyBox init 依赖 /etc/inittab,若该文件缺失或格式错误,控制台可能无法拉起 shell。
  • 文件系统类型不匹配:把 ext4 分区写成 rootfstype=jffs2,内核会直接挂载失败。

复盘建议: 排查启动问题时,按“Bootloader 是否加载成功 → 内核是否解压运行 → root= 是否被正确解析 → 根文件系统是否挂载 → init 是否执行”的顺序逐段确认。串口日志中的每一行都是定位接力断点的关键证据,尤其是 Kernel command line 这一行,它直接反映了实际生效的 bootargs。

理解了 Bootloader、Kernel、Rootfs 的协作关系与加载方式,接下来用 BusyBox 构建根文件系统时,就能明确“目录结构要满足 init 查找规则、库文件要满足动态链接需求、inittab 要满足 BusyBox init 的解析格式”,从而让定制出的 rootfs 真正能被内核顺利接管。