/etc/inittab 文件深度解析:语法规则与 action 字段详解
Busybox 的 init 进程启动后,第一件事就是去读 /etc/inittab。这个文件虽然小,但它是整个启动流程的调度中心。我见过不少工程师把 inittab 当成一个简单的「启动脚本列表」,结果系统跑起来各种问题——进程反复重启、关机卡死、控制台没反应。说白了,你没搞懂 action 字段,就没办法真正掌控启动流程。
这一节,我们就把 inittab 的语法和每个 action 字段掰开揉碎。嗯,我会结合我实际踩过的坑来讲,你跟着走一遍,以后看到 inittab 就不会发怵了。
3.1 inittab 的语法规则
先看一个典型的 inittab 条目:
ttyS0::respawn:/sbin/getty -L ttyS0 115200 vt100
每一行由 4 个字段组成,用冒号 : 分隔。格式如下:
id:runlevels:action:process
| 字段 | 含义 | 说明 |
|---|---|---|
| id | 标识符 | 通常用作控制台设备名(如 ttyS0),也可为空 |
| runlevels | 运行级别 | Busybox 中基本忽略,通常留空 |
| action | 动作类型 | 决定进程如何被管理(核心字段) |
| process | 要执行的命令 | 可以是脚本路径或可执行文件 |
::action:process 的形式,简洁明了。
空行和以 # 开头的行会被忽略。这个和大多数配置文件一样,没什么特别的。
3.2 action 字段详解
action 字段是 inittab 的灵魂。Busybox 支持 8 种 action,每种决定了 init 如何对待后面的 process。我按执行时机把它们分成三组来讲。
3.2.1 系统初始化类:sysinit、wait、once
sysinit —— 系统启动时最先执行的动作。init 进程会等待它执行完毕,才会继续处理其他条目。通常用来挂载文件系统、设置主机名、初始化硬件。
::sysinit:/etc/init.d/rcS
wait —— 在 sysinit 之后执行,init 会等待它完成。和 sysinit 的区别在于,wait 可以出现在多个条目中,而且可以指定运行级别(虽然 Busybox 不常用)。我通常把文件系统检查、udev 初始化这类任务放在 wait 里。
::wait:/sbin/mdev -s
once —— 执行一次,但 init 不等待它完成。适合启动那些不需要阻塞后续流程的服务,比如启动一个看门狗守护进程。
::once:/usr/sbin/watchdog -t 10 /dev/watchdog
3.2.2 进程管理类:respawn、askfirst
这两个是嵌入式系统里最常用的 action,也是最容易出问题的地方。
respawn —— 进程终止后自动重启。典型的用法是启动 getty 提供登录终端:
ttyS0::respawn:/sbin/getty -L ttyS0 115200 vt100
进程退出后,init 会重新 fork 一个。但这里有个坑——如果进程频繁崩溃,init 会检测到「快速重启」现象,然后暂停 respawn 一段时间。我曾经遇到一个情况,某个守护进程每 5 秒挂一次,init 在 10 次后直接放弃了 respawn,终端就再也登不进去了。
-f 参数忽略挂断信号,或者用 askfirst 替代。
askfirst —— 和 respawn 几乎一样,唯一的区别是:在启动进程前,先在终端上打印 "Please press Enter to activate this console.",等待用户按回车后才执行。这个设计是为了多终端场景下,不让所有终端同时启动 getty 浪费资源。
ttyS0::askfirst:/sbin/getty -L ttyS0 115200 vt100
我个人习惯在调试阶段用 askfirst,量产阶段换成 respawn。因为调试时经常需要插拔串口,askfirst 能避免 getty 在终端还没准备好时就启动。
3.2.3 系统控制类:ctrlaltdel、shutdown、restart
这三个 action 处理的是系统生命周期中的特殊事件。
ctrlaltdel —— 当按下 Ctrl+Alt+Del 组合键时执行。在嵌入式设备上,这个组合键通常被映射到串口终端。我一般把它配置成重启或执行一个清理脚本:
::ctrlaltdel:/sbin/reboot
或者更温和一点:
::ctrlaltdel:/bin/echo "Ctrl+Alt+Del pressed, ignoring"
shutdown —— 系统关机或重启时执行。init 会先终止所有进程,然后执行 shutdown 条目中的命令。通常用来卸载文件系统、关闭网络、保存日志。
::shutdown:/sbin/swapoff -a
::shutdown:/bin/umount -a -r
注意,shutdown 可以有多个条目,init 会按顺序执行。
restart —— 当 init 进程收到 SIGHUP 信号时执行。这个在 Busybox 里用得不多,但如果你需要动态重载 inittab,可以这样用:
::restart:/sbin/init
执行 kill -HUP 1 后,init 会重新读取 inittab 并重启自身。
3.3 action 字段对比表
| action | 执行时机 | 是否等待 | 是否重启 | 典型用途 |
|---|---|---|---|---|
| sysinit | 系统启动最先 | 是 | 否 | 挂载文件系统、rcS 脚本 |
| wait | sysinit 之后 | 是 | 否 | mdev、文件系统检查 |
| once | 任意时刻 | 否 | 否 | 看门狗、日志服务 |
| respawn | 任意时刻 | 否 | 是 | getty、守护进程 |
| askfirst | 任意时刻 | 否 | 是 | 交互式终端 |
| ctrlaltdel | Ctrl+Alt+Del 按下 | 否 | 否 | 重启或忽略 |
| shutdown | 关机/重启时 | 是 | 否 | 卸载文件系统 |
| restart | init 收到 SIGHUP | 是 | 否 | 重载 inittab |
3.4 一个完整的 inittab 示例
下面是我在一个 ARM 项目里用过的 inittab,你可以参考:
# 系统初始化
::sysinit:/etc/init.d/rcS
# 等待 mdev 完成设备扫描
::wait:/sbin/mdev -s
# 启动看门狗(不等待)
::once:/usr/sbin/watchdog
# 串口终端(按回车激活)
ttyS0::askfirst:/sbin/getty -L ttyS0 115200 vt100
# 调试串口(自动重启)
ttyS1::respawn:/sbin/getty -L ttyS1 115200 vt100
# Ctrl+Alt+Del 直接重启
::ctrlaltdel:/sbin/reboot
# 关机时卸载文件系统
::shutdown:/bin/umount -a -r
3.5 inittab 执行流程 SVG 图
下面这张图展示了 init 进程解析 inittab 后,各个 action 的执行顺序和关系:
3.6 常见问题与避坑
- 进程反复重启导致 init 暂停 respawn:Busybox 的 init 会记录进程退出次数,如果 10 秒内退出超过 5 次,会暂停 respawn 30 秒。解决办法是检查进程为什么频繁退出,而不是盲目调参。
- sysinit 卡死导致系统无法启动:sysinit 是同步执行的,里面的脚本必须保证能正常结束。我习惯在 sysinit 脚本开头加
exec > /dev/console 2>&1,这样能看到输出,方便调试。 - 多个终端同时启动 getty 浪费资源:用 askfirst 替代 respawn,只有用户按回车后才启动 getty。这在只有 1-2 个串口的设备上效果很好。
- shutdown 条目不执行:检查是否在关机命令中正确传递了信号。Busybox 的 init 只会在收到 SIGTERM 或 SIGUSR1 时执行 shutdown 条目。
reboot 或 poweroff,否则系统会陷入「启动-关机-启动」的死循环。我见过有人把关机脚本误放到 sysinit 里,结果设备一上电就关机,折腾了两天才找到原因。
好了,inittab 的语法和 action 字段就讲到这里。你只要记住:sysinit 是地基,wait 是承重墙,respawn 是日常运维,shutdown 是收尾。把这几个搞明白,你的启动脚本就不会乱套。