嵌入式Linux启动流程全拆解:从U-Boot上电到应用层启动的完整链路追踪
嵌入式Linux上电后是一条接力链:BootROM→SPL→U-Boot→Kernel→rootfs→init→应用。任一阶段无串口输出,都要按阶段二分,而不是盲目重烧整包。
一、BootROM与SPL:把运行环境搭起来
SoC上电先跑片内ROM,按拨码/熔丝选择SD、eMMC、NAND、SPI;多数A核再搬SPL到SRAM,SPL只做最少量工作:关看门狗、配时钟、初始化DDR、把完整U-Boot搬到DDR并跳转。SPL阶段几乎没有日志,卡住优先查供电、晶振、DDR时序、启动介质脚。
U-Boot Proper起来后做重定位、环境变量、串口、存储/网络外设,最后进main_loop。自动启动靠两个变量:
printenv bootcmd
printenv bootargs
bootcmd是“怎么加载启动”,bootargs是“传给内核什么参数”。 SD/eMMC ARM样机示例:
setenv loadaddr 0x42000000
setenv fdtaddr 0x43000000
setenv bootcmd 'fatload mmc 0:1 ${loadaddr} zImage; fatload mmc 0:1 ${fdtaddr} board.dtb; bootz ${loadaddr} - ${fdtaddr}'
setenv bootargs 'console=ttyS0,115200 earlycon=uart8250,mmio32,0x44e09000 root=/dev/mmcblk0p2 rw rootwait initcall_debug=1'
saveenv
bootz用于zImage+dtb;uImage用bootm;ARM64裸Image用booti。U-Boot会把bootargs写进DTB的/chosen/bootargs,再把DTB地址通过寄存器交内核,ARM32常走r2、ARM64走x0。
二、内核解压与start_kernel
U-Boot跳进内核入口后,压缩镜像先自解压,再跑体系结构汇编,进入C函数start_kernel():初始化固定内存memblock、MMU页表、中断向量、调度器、timer,再setup_arch()解析DTB,建立platform/device/driver模型。 早期没控制台时靠earlycon看日志,正式控制台由console=激活。
设备树决定“有什么硬件”:CPU、内存、UART、I2C、SPI、网卡、 regulators、clock、gpio都在dts里;驱动按compatible匹配probe。若卡在“Starting kernel…”之后无输出,先确认earlycon地址与UART时钟,再用initcall_debug看卡在哪个initcall:
# 内核cmdline追加
initcall_debug printk.time=1 loglevel=7 ignore_loglevel
# 启动后
dmesg | grep -A3 -B1 "initcall"
initcall_debug会按调用顺序打印各子系统初始化耗时,驱动probe死等、I2C时钟未起、regulator死锁都能在这里暴露。
三、挂载根文件系统
内核按root=找根设备,常见有四类:
eMMC/SD:root=/dev/mmcblk0p2,ext4/squashfs均可,rootwait等设备枚举完再挂;
NAND/UBI:root=ubi0:rootfs,需内置UBI/UBIFS驱动,不能等模块;
网络调试:root=/dev/nfs,配合ip=、nfsroot=,适合样机迭代;
内存临时根:initramfs内嵌cpio,先跑/init做驱动、解密、RAID,再switch_root到真根。
若报“VFS: Unable to mount root fs”,按三件事查:root=设备名是否和分区一致、对应块/文件系统驱动是否编进内核而非模块、DTB里存储控制器和时钟是否probe成功。只读系统可先ro挂根,进init后再remount rw,降低掉电风险。
initramfs最小切换思路:
mount -t proc proc /proc
mount -t sysfs sysfs /sys
mount /dev/mmcblk0p2 /new_root
exec switch_root /new_root /sbin/init
四、init到应用层
内核找PID1顺序受init=覆盖,默认/sbin/init→/etc/init→/bin/init→/bin/sh。轻量系统用BusyBox init,读/etc/inittab:
::sysinit:/etc/init.d/rcS
::respawn:/sbin/getty 115200 ttyS0
::restart:/sbin/init
::ctrlaltdel:/sbin/reboot
rcS里挂proc/sys/devpts、配置网络、调时钟、启动业务守护。工业网关常把业务拆成独立脚本,先网络再MQTT再采集,避免依赖倒置。
用systemd时看target和服务单元:
[Unit]
Description=Edge Collector
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
ExecStart=/usr/bin/collector --conf /etc/collector.conf
Restart=on-failure
[Install]
WantedBy=multi-user.target
启用并查启动耗时:
systemctl enable collector.service
systemctl start collector.service
systemctl --failed
systemd-analyze blame
systemd-analyze critical-chain collector.service
blame看单服务耗时,critical-chain看依赖关键路径;启动慢常是网络等待、文件系统fsck、数据库恢复或非必要服务并行度不够。
五、全链路排查清单
完全无串口:BootROM介质、SPL DDR、U-Boot环境变量、串口stdout/stderr是否设置;
进内核即停:earlycon是否匹配、DTB地址是否和bootcmd一致、内存节点是否准确;
驱动不跑:dmesg看deferred、clock/regulator/gpio依赖,of_match是否拼错;
根挂不上:root=、rootfstype、rootwait、块驱动内置、initramfs是否缺模块;
init起不来:用init=/bin/sh进救援,检查/sbin/init存在、动态库完整、inittab/rcS权限;
应用自起后崩:单独跑二进制看ldd、配置文件、/var日志、systemd Restart与journalctl -u。
把bootargs、DTB、initramfs、rcS/systemd单元全部版本化,每次只改一个变量复测,启动问题基本都能收敛到“某一行参数或某一个probe”。





