BusyBox init启动脚本与系统加速 - 第2节
init 进程启动流程:Linux 内核启动后第一个用户态进程
内核启动完,最后一步就是调用 /sbin/init。这时候,系统从内核态切换到用户态。说白了,内核把接力棒交给了 init 进程。init 的 PID 永远是 1,它是所有用户进程的祖先。
我刚开始做嵌入式时,以为 init 就是个花架子。直到有一次,产品在产线测试时频繁死机,查了两天才发现是 init 配置不当,导致关键服务没启动起来。嗯,从那以后我再也不敢小看这个 PID 1 了。
内核怎么找到 init 的?
内核在挂载根文件系统后,会按顺序尝试执行这几个路径:
/sbin/init— 标准路径,大部分嵌入式系统用这个/etc/init— 备选/bin/init— 再备选/bin/sh— 最后的保底方案
如果全失败了,内核会 panic。你想想看,连个 shell 都起不来,这系统基本就废了。
/sbin/init 之外,再软链接一个 /bin/init 指向 busybox。万一主路径被误删,系统还能救回来。
Busybox init 的启动顺序
Busybox 的 init 比 SysV init 简单得多。它不跑 rc 脚本,不管理运行级别(至少不按传统方式)。它的核心逻辑就三步:
- 读取配置文件 — 解析
/etc/inittab - 执行系统初始化 — 跑
sysinit动作 - 进入等待循环 — 处理
respawn、askfirst、once等动作
下面这张图把整个流程串起来了:
/etc/inittab 配置文件详解
inittab 是 Busybox init 的灵魂。没有它,init 就不知道要干什么。它的格式很简单,每行一个条目,用冒号分隔四个字段:
id:runlevels:action:process
我见过不少新手把 inittab 写成 shell 脚本,结果系统起不来。记住,它不是脚本,是配置表。
四个字段的含义
| 字段 | 含义 | 说明 |
|---|---|---|
| id | 唯一标识符 | 1-4 个字符,用于控制 tty 或作为进程标识。Busybox 中通常用 tty 名称,如 ttyS0 |
| runlevels | 运行级别 | Busybox 忽略此字段,但为了兼容性可以留空或填任意值 |
| action | 动作类型 | 决定何时以及如何执行 process 字段指定的命令 |
| process | 要执行的命令 | 可以是可执行文件路径或 shell 命令 |
ttyS0 条目,结果第二个覆盖了第一个,导致 getty 没起来。查了整整一下午。
八种 action 类型
这是 inittab 最核心的部分。不同的 action 决定了进程的启动时机和行为。我按使用频率排了个序:
| action | 触发时机 | 进程行为 | 典型用途 |
|---|---|---|---|
| sysinit | init 启动后立即执行 | 执行一次,等待完成 | 挂载文件系统、设置主机名、初始化网络 |
| respawn | 进程退出后自动重启 | 无限重启 | getty 登录 shell、关键守护进程 |
| askfirst | 类似 respawn,但先打印提示 | 等待用户按 Enter 后才启动 | 串口控制台、调试终端 |
| once | 启动时执行一次 | 不等待完成,不重启 | 启动非关键服务、清理临时文件 |
| wait | 启动时执行一次 | 等待进程退出后才继续 | 初始化脚本、依赖后续步骤的任务 |
| ctrlaltdel | 收到 Ctrl+Alt+Del 信号 | 执行一次 | 重启或关机命令 |
| shutdown | 系统关机时 | 执行一次 | 卸载文件系统、关闭服务 |
| restart | init 收到 SIGHUP 信号 | 执行一次,通常用于重启 init | 重新加载配置 |
一个典型的 inittab 实例
下面是我在项目中常用的模板。它覆盖了从系统初始化到用户登录的完整流程:
# /etc/inittab — Busybox init 配置文件
# 格式: id:runlevels:action:process
# 系统初始化 — 挂载文件系统、设置环境
::sysinit:/etc/init.d/rcS
# 串口控制台 — 按 Enter 才启动,适合调试
ttyS0::askfirst:/sbin/getty -L ttyS0 115200 vt100
# 虚拟终端 — 自动重启登录进程
tty1::respawn:/sbin/getty 38400 tty1
# 非关键服务 — 启动一次,挂了不管
::once:/usr/sbin/lighttpd -f /etc/lighttpd.conf
# 关机时执行 — 安全卸载
::shutdown:/sbin/swapoff -a
::shutdown:/bin/umount -a -r
# 重启时执行 — 重新挂载
::restart:/sbin/init
关键点:
sysinit条目没有 id,用空字符串表示。Busybox 允许这样。askfirst和respawn的区别:前者会打印 "Please press Enter to activate this console.",然后等待用户按键。后者直接启动,不等待。shutdown条目按顺序执行,所以先关 swap 再卸载文件系统。
sysinit 的执行细节
sysinit 是系统初始化的入口。它通常指向一个 shell 脚本,比如 /etc/init.d/rcS。这个脚本里会做:
- 挂载
/proc、/sys、/dev、/tmp - 设置主机名
- 配置网络接口
- 启动 udev 或 mdev 管理设备节点
- 加载内核模块
我个人习惯在 rcS 脚本开头加一行 echo "=== rcS started ===" > /dev/console。这样如果系统卡在初始化阶段,至少能看到脚本有没有被执行。
exit。如果脚本提前退出,init 会认为 sysinit 执行完毕,然后继续处理后续条目。我曾经在 rcS 里写了个 exit 1 用于错误处理,结果后面的 respawn 条目全跑了,系统状态一团糟。
respawn 与 askfirst 的选择
这两个 action 最容易混淆。我简单总结一下:
- respawn:进程退出后立即重启。适合必须一直运行的服务,比如 getty、看门狗守护进程。
- askfirst:进程退出后重启,但启动前先打印提示并等待用户按键。适合串口控制台,防止系统启动时自动弹出登录提示,干扰其他输出。
你想想看,如果串口上配了 respawn,系统启动时就会直接弹出 login 提示。但如果你在调试内核启动日志,这些提示会刷掉你的调试信息。用 askfirst 就能避免这个问题。
shutdown 和 restart 的注意事项
shutdown 条目在系统关机或重启时执行。Busybox init 会按 inittab 中 shutdown 条目的顺序依次执行。这里有个细节:shutdown 条目执行时,init 不会等待它们完成。所以如果你需要确保某个操作完成,最好在脚本里自己加同步机制。
restart 条目在 init 收到 SIGHUP 信号时触发。通常用来重新加载 inittab。我一般会在 restart 条目里写 /sbin/init,这样相当于重启 init 进程本身。
killall -9 杀掉所有进程。结果 init 自己也被杀了,系统直接挂死。正确的做法是让 init 来管理进程生命周期,不要自己乱杀。
inittab 的调试技巧
inittab 配置错了,系统可能起不来。这时候怎么办?我分享几个经验:
- 内核参数加
init=/bin/sh:绕过 init,直接进 shell。然后手动执行/sbin/init -i查看 inittab 解析结果。 - 检查语法:Busybox init 对 inittab 的解析很严格。每行不能有多余空格,字段之间只能用冒号分隔。
- 用
strace跟踪:如果系统能部分启动,用strace -p 1看 init 在干什么。 - 日志输出:在 sysinit 脚本里加
set -x,把执行过程输出到串口或日志文件。
嗯,这些技巧帮我省了不少时间。特别是第一条,简直是嵌入式调试的救命稻草。
总结一下
inittab 是 Busybox init 的配置核心。它用简单的四字段格式,定义了系统从启动到关机的完整行为。理解这八种 action 的差异,你就能精确控制每个进程的启动时机和生命周期。