/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 要执行的命令 可以是脚本路径或可执行文件
小提示:Busybox 的 init 是简化版,runlevels 字段基本不生效。我习惯直接留空,写成 ::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
重点:sysinit 只执行一次,且必须等它跑完。我曾经在一个项目里把网络配置放到了 sysinit 里,结果网络初始化卡住,整个系统起不来。后来我把网络相关的挪到了 wait 里,sysinit 只做最核心的挂载和基础环境准备。

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,终端就再也登不进去了。

避坑指南:我曾经在产线上调试一个设备,发现 ttyS0 的 getty 总是莫名其妙消失。查了半天,原来是串口线接触不良,getty 一启动就收到乱码然后退出,触发 respawn 循环。最后 init 罢工了。解决方案是在 getty 命令里加 -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 的执行顺序和关系:

inittab action 执行流程 inittab action 执行流程 init 读取 inittab sysinit(等待完成) wait(等待完成) once(不等待) respawn/askfirst ctrlaltdel(等待) respawn/askfirst 进程退出后自动重启 askfirst 额外等待用户按回车 shutdown(关机时触发) restart(SIGHUP 时触发) 实线 = 正常启动流程,虚线 = 特殊事件触发

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 条目。
注意:千万不要在 sysinit 里执行 reboot 或 poweroff,否则系统会陷入「启动-关机-启动」的死循环。我见过有人把关机脚本误放到 sysinit 里,结果设备一上电就关机,折腾了两天才找到原因。

好了,inittab 的语法和 action 字段就讲到这里。你只要记住:sysinit 是地基,wait 是承重墙,respawn 是日常运维,shutdown 是收尾。把这几个搞明白,你的启动脚本就不会乱套。