2. 嵌入式Linux启动流程
嵌入式Linux的启动过程,本质上是一条从“裸机硬件”到“可用用户空间”的接力链。很多初学者把启动失败笼统归因于“系统没起来”,但在工程实践中,必须精确判断问题发生在哪一棒:是Bootloader没有正确加载内核,还是内核找不到根文件系统,抑或是init进程没有拉起关键服务。理解Bootloader、内核、RootFS三者的职责边界,以及init进程如何衔接内核态与用户态,是后续裁剪BusyBox、定制RootFS的前提。
一、三阶段职责划分:谁负责什么
嵌入式Linux启动通常划分为三个阶段,每个阶段有明确的输入、输出和失败特征。
- Bootloader阶段:上电后最先运行,负责初始化DDR、时钟、串口等最基础硬件,然后从Flash、SD卡、网络或UART加载内核镜像(zImage/Image)和设备树(.dtb),最后跳转到内核入口。它不关心文件系统里有什么应用。
- 内核阶段:解压自身、初始化内存管理、调度、驱动模型,挂载根文件系统。内核只负责“让系统具备运行用户程序的能力”,不负责决定要运行哪些业务服务。
- RootFS与用户空间阶段:提供
/bin、/sbin、/etc、/lib等目录和可执行程序,由内核启动的第一个用户态进程init,按配置拉起服务、挂载、网络、登录等,最终形成可用系统。
可以用一句话概括:Bootloader把内核“搬进来”,内核把RootFS“挂起来”,init把用户空间“跑起来”。
二、init进程:内核态与用户态的交接点
内核在完成初始化后,会尝试执行第一个用户态程序。这个程序由内核命令行参数init=指定,若未指定则按顺序查找/sbin/init、/etc/init、/bin/init、/bin/sh。在嵌入式系统中,这个程序通常就是BusyBox提供的init。
BusyBox init的行为由/etc/inittab驱动,其核心动作类型包括:
sysinit:系统初始化阶段执行一次,通常指向/etc/init.d/rcS。respawn:进程退出后自动重启,适合常驻服务,如ttyS0::respawn:/sbin/getty。askfirst:等待用户按键后再启动,常用于调试串口登录。once:只执行一次,不重启。shutdown:系统关机时执行。
一个典型的/etc/inittab片段如下:
# /etc/inittab
::sysinit:/etc/init.d/rcS
::respawn:/sbin/getty -L ttyS0 115200 vt100
::ctrlaltdel:/sbin/reboot
::shutdown:/bin/umount -a -r
而/etc/init.d/rcS则负责挂载伪文件系统、配置网络、启动应用:
#!/bin/sh
mount -t proc none /proc
mount -t sysfs none /sys
mount -t tmpfs none /tmp
ifconfig lo 127.0.0.1 up
ifconfig eth0 192.168.1.10 up
/usr/sbin/telnetd -l /bin/sh &
/opt/app/myapp &
三、启动阶段对照表:现象、原因与定位手段
| 阶段 | 典型输出/现象 | 常见原因 | 定位手段 |
|---|---|---|---|
| Bootloader | 串口无输出,或停在U-Boot提示符 | DDR参数错误、启动介质选择错误、bootcmd未配置 | 检查printenv、bootargs、bootcmd |
| 内核加载 | Starting kernel ... 后无输出 | 内核镜像地址错误、设备树不匹配、串口驱动未使能 | 核对bootm地址、console=参数 |
| 根文件系统挂载 | Kernel panic - not syncing: VFS: Unable to mount root fs | root=参数错误、存储驱动缺失、文件系统类型不匹配 | 检查root=/dev/mmcblk0p2、rootfstype=ext4 |
| init启动 | Kernel panic - not syncing: No init found | /sbin/init缺失、权限错误、动态库缺失 | 用init=/bin/sh进入应急shell排查 |
| 用户空间服务 | 系统能登录但业务未启动 | rcS未执行、脚本权限不足、依赖库缺失 | 查看ps、dmesg、手动执行脚本 |
四、实战场景:从“卡在No init found”到定位RootFS问题
某项目使用BusyBox构建RootFS,内核启动后报错:
[ 2.134567] Kernel panic - not syncing: No init found.
Try passing init= option to kernel.
排查思路如下:
- 确认内核命令行:
cat /proc/cmdline显示root=/dev/mmcblk0p2 rootfstype=ext4,参数正确。 - 在U-Boot中临时修改启动参数为
init=/bin/sh,重新启动后成功进入shell,说明内核和根文件系统挂载均正常。 - 在应急shell中执行
ls -l /sbin/init,发现该文件不存在,只有/bin/busybox。 - 检查RootFS制作脚本,发现创建
/sbin/init软链接的步骤被遗漏。 - 补上
ln -s /bin/busybox /sbin/init后重新打包镜像,系统正常启动。
应急技巧:当init启动失败时,在内核命令行追加
init=/bin/sh或rdinit=/bin/sh,可以绕过init直接进入shell,快速判断问题出在RootFS内容还是内核配置。
五、常见误区与复盘要点
- 误区一:把“内核启动”等同于“系统启动”。内核打印出
Freeing unused kernel memory只代表内核初始化完成,真正决定系统可用性的是init和用户空间服务。 - 误区二:忽略动态库依赖。BusyBox若编译为动态链接,RootFS中必须包含
/lib/ld-linux.so.3等加载器和依赖库,否则init无法执行。 - 误区三:inittab与rcS职责混淆。inittab负责“启动哪些进程”,rcS负责“启动前做什么配置”,两者应分层清晰,避免把所有逻辑堆在inittab中。
- 复盘要点:每次启动失败,先看串口最后一行输出,再判断属于哪个阶段,最后用最小化手段(如
init=/bin/sh)缩小范围,而不是盲目重新烧写整个镜像。
掌握这条启动链路后,后续裁剪BusyBox、精简RootFS、替换init方案时,就能清楚知道每一步改动会影响哪个阶段,从而做到有的放矢。