3. 嵌入式Linux系统启动流程

嵌入式Linux的启动不是“上电即进Shell”,而是一条严格分阶段的接力链:芯片内部固件先把最简存储介质中的Bootloader搬进内存,Bootloader再准备内核运行环境并传递参数,内核完成自解压、早期初始化、驱动探测与挂载根文件系统,最后由用户空间的init进程启动服务并进入Busybox提供的Shell或应用。理解这条链路,是后续做Busybox驱动集成与板卡适配的前提——因为任何一环的参数、路径或设备节点不匹配,系统都可能停在“Kernel panic”或“No init found”。

一、启动链的五个关键阶段

典型ARM/ARM64嵌入式板卡的启动链可拆为:ROM Code → Bootloader(SPL/TPL + U-Boot)→ Linux Kernel → Device Tree → Rootfs + Busybox init。它们不是并列关系,而是前后依赖的时序关系。

  • ROM Code:芯片出厂固化,按拨码或eFuse顺序扫描SD、eMMC、SPI Flash、USB等启动介质,加载第一级引导头。
  • Bootloader:通常分两级。SPL负责初始化DDR并加载完整U-Boot;U-Boot负责网络、存储、环境变量、加载内核与设备树,并通过bootargs传递启动参数。
  • Kernel:自解压后进入start_kernel,完成内存管理、调度、中断、时钟初始化,再根据设备树匹配并probe驱动。
  • Device Tree:以.dts/.dtb描述板级硬件拓扑,内核据此知道有哪些UART、I2C、GPIO、MMC控制器及其地址、中断、时钟。
  • Rootfs与Busybox:内核挂载根文件系统后执行/init或/sbin/init,Busybox以单一可执行文件提供sh、ls、mount、ifconfig等命令,并常作为init程序完成服务启动。
嵌入式Linux启动链:从ROM Code到Busybox用户空间 ROM Code 固化引导 扫描启动介质 Bootloader SPL初始化DDR U-Boot加载内核/DTB Linux Kernel 自解压/start_kernel 驱动probe Device Tree .dts → .dtb 描述板级硬件 Rootfs 挂载根文件系统 执行/init U-Boot 传递启动参数(bootargs) console=ttyS0,115200 root=/dev/mmcblk0p2 rw rootwait init=/sbin/init Busybox 在用户空间的角色 /sbin/init → /etc/inittab → rcS 脚本 → 启动服务 → 进入Shell 单一二进制提供 sh、ls、mount、ifconfig、insmod、mdev 等命令

二、Bootloader与内核的交接:参数比代码更易出错

U-Boot阶段最关键的产物是内核镜像、设备树二进制和bootargs。常见加载命令如下:

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

其中root=决定根文件系统位置,rootwait让内核等待MMC枚举完成,init=指定第一个用户空间进程。若把root=/dev/mmcblk0p2误写成/dev/mmcblk1p2,内核会报“VFS: Cannot open root device”,此时Busybox根本没有机会运行。

三、设备树:内核与板卡之间的“硬件合同”

设备树把板级差异从内核源码中剥离。以UART为例,.dts中描述寄存器地址、中断号、时钟和引脚复用;内核启动时由of_platform_populate扫描节点,匹配compatible字符串后调用对应驱动probe。若设备树中缺少某个节点,驱动不会报“找不到硬件”,而是根本不创建platform_device,表现为“设备不存在”。

&uart1 {
    compatible = "fsl,imx6ul-uart", "fsl,imx6q-uart";
    reg = <0x02020000 0x4000>;
    interrupts = <GIC_SPI 26 IRQ_TYPE_LEVEL_HIGH>;
    clocks = <&clks IMX6UL_CLK_UART1_IPG>,
             <&clks IMX6UL_CLK_UART1_SERIAL>;
    status = "okay";
};

四、根文件系统与Busybox init:启动链的最后一公里

内核挂载根文件系统后,会依次尝试/sbin/init、/etc/init、/bin/init、/bin/sh。在Busybox方案中,通常把/sbin/init软链接到/bin/busybox,由Busybox根据argv[0]判断进入init模式,读取/etc/inittab:

::sysinit:/etc/init.d/rcS
::respawn:/sbin/getty -L ttyS0 115200 vt100
::ctrlaltdel:/sbin/reboot
::shutdown:/bin/umount -a -r

rcS脚本中常通过mdev -s生成/dev节点,通过insmod加载板卡驱动模块。若根文件系统中缺少/dev/console,内核会提示“Warning: unable to open an initial console”,虽然系统可能继续启动,但串口交互会异常。

五、启动链关键对象与排障对照

阶段 关键对象 典型失败现象 排查入口
ROM Code 启动介质、拨码 串口无任何输出 确认SD/eMMC启动模式与镜像头
Bootloader SPL、U-Boot、bootargs 停在U-Boot命令行或DDR初始化失败 检查DDR配置、环境变量、加载地址
Kernel zImage、驱动probe Kernel panic、驱动未加载 看dmesg、compatible匹配、时钟/中断
Device Tree .dtb、节点status 设备不存在、引脚无功能 反编译dtb核对节点与pinctrl
Rootfs/Busybox /init、inittab、rcS No init found、无法进入Shell 检查root=、init=、/dev/console、权限
实战建议:调试启动链时,先保证串口能打印完整U-Boot和内核日志,再逐段核对“加载地址—bootargs—设备树—根文件系统”四要素。很多“驱动集成失败”其实不是驱动代码问题,而是设备树节点未使能或根文件系统中缺少对应的.ko与设备节点。
常见误区:把Busybox仅当作命令集合,忽略它作为init的职责;或者只改内核配置却不同步更新.dtb,导致驱动probe时找不到platform_device。启动链是整体契约,任何一段的版本、路径和参数都必须与相邻阶段一致。

综上,Bootloader负责“把内核和板卡描述送进内存”,内核负责“按设备树初始化硬件并挂载根文件系统”,Busybox则在用户空间完成“从init到Shell”的最后一公里。后续做Busybox驱动集成时,驱动模块的加载时机、设备节点生成和启动脚本顺序,都必须放回这条启动链中审视,才能保证板卡从上电到业务进程稳定运行。