嵌入式Linux的根文件系统,从init进程开始理解

📅 2026/7/7 3:43:20
嵌入式Linux的根文件系统,从init进程开始理解
你有没有想过嵌入式Linux板子上电之后内核跑完到底是谁启动了你的应用程序uboot引导内核内核解压、初始化硬件、挂载根文件系统——这些过程大家多少都听过。但内核交出控制权之后发生了什么如果答不上来那我们对整个嵌入式系统的理解就缺了最关键的一环。我们来拆这个环节。内核最后干了什么内核启动的最后阶段它会调用run_init_process。这个函数做的事情非常朴素在当前进程空间pid1里加载一个可执行文件。默认路径按优先级依次是/sbin/init /etc/init /bin/init /bin/sh找到第一个能用的就停下来。如果全都找不到内核panic。所以答案来了——pid1的init进程就是内核交出去的接力棒。这个进程只能有一个不能kill不能fork新的pid1它在系统里的地位是独一无二的。三个不同的init世界问题来了这个init到底是什么在嵌入式Linux里不同场景下它完全不一样。BusyBox init —— 最轻量的选择大部分基于Buildroot的嵌入式系统用这个。它的核心逻辑写在一行里// busybox/init/init.c 简化逻辑 pid_t pid fork(); if (pid 0) { /* 子进程 */ execvp(cmd-argv[0], cmd-argv); _exit(127); } else { /* 父进程waitpid处理SIGCHLD */ waitpid(pid, status, 0); }每个子进程死了init就重启它——用waitpid的WIFEXITED判断退出原因如果是crash非正常退出可以在/etc/inittab里配respawn让它自动拉起。这也是为什么嵌入式设备里某个daemon挂了能自己活过来的原因。/etc/inittab长这样::sysinit:/etc/init.d/rcS ttyS0::respawn:/sbin/getty -L ttyS0 115200 vt100 ::shutdown:/bin/umount -a -r一个有意思的设计是sysinit只跑一次respawn的进程死了就重跑askfirst会在终端上打印Please press Enter to activate this console才启动shell——省电模式下用得着。systemd —— 复杂但强大如果跑的是Yocto或者标准Linux发行版init就是systemd。它的逻辑不是fork-exec的死循环而是一场依赖关系图的启动编排。systemd做的事情可以用一句话概括把启动过程抽象成一个个unit用并行代替串行。每个.service文件里写着[Unit] DescriptionMy daemon Afternetwork.target [Service] ExecStart/usr/bin/my_daemon Restarton-failureRestarton-failure这个选项很有意思。相比于BusyBox init那种无差别respawnsystemd可以区分正常退出和异常退出。exit code为0不重启segfault了才重启。而且可以限制重启频次——StartLimitIntervalSec10s配合StartBurstLimit3防止crash loop把CPU占满。直接跑shell有些极端资源受限的场景——比如4MB Flash的OpenWrt路由器——连init都省了。内核参数里直接写init/bin/sh。这时候pid1就是个shell你exit就等于关机。为什么pid1这么特殊一个常见的误区是那我也可以手动kill 1号进程然后重启。试试就知道了——kill -9 1在普通Linux上会被内核静默忽略。内核在kernel/signal.c里有硬编码检查if (task init_task sig SIGKILL) return -EINVAL;这一行保护了整个系统的根进程。init没了内核就没人收养孤儿进程了——所有孤儿进程的ppid会变成1它们结束时需要init来waitpid清理僵尸。如果init挂了僵尸堆积内核内存泄漏。换个角度理解init本质上就是个专门回收孤儿遗产的收容站。嵌入式场景下怎么看init的行为调试嵌入式Linux的启动问题时init往往是第一个需要检查的环节。板子启动到一半卡住了怎么知道是内核挂了还是init崩了加initcall_debug和printk.timey看内核日志的最后输出。如果看到VFS: Mounted root之后就没动静了大概率是init没起来。这时候检查根文件系统的路径root/dev/mmcblk0p2里面的设备节点对不对文件系统格式对不对。如果init起起来了但应用没跑看init的配置文件。用readelf看init的符号如果是BusyBox在板子上跑busybox --help看编译了哪些applet。如果是systemdsystemctl list-units --failed一次就能找到失败的service。另外一个小技巧在Buildroot的配置里打开BR2_INIT_BUSYBOXy的同时检查/etc/inittab里是不是每个respawn的进程都对应板子上实际的设备节点。vp9编解码这种后台进程经常开出来没有节点然后就循环fork-fail把CPU吃满。一个思考题从头写一个最小init程序其实只需要20行C代码——fork一个子进程执行shell然后父进程while(1) waitpid。但它能工作。真正复杂的是怎么处理SIGTERM的广播、怎么在关机前umount文件系统、怎么在子进程crash时记录日志、怎么支持telnet登录时动态启动getty。BusyBox init的代码才不到2000行但把边缘情况全都覆盖了。下次如果要在板子上加一个开机自启动的daemon不妨想一想它应该配成respawn还是once如果是once它挂了谁来兜底这是今天想分享的内容欢迎讨论。