4. rcS启动脚本分析:/etc/init.d/rcS脚本的作用,rcS的执行时机,rcS的基本结构
好,咱们进入第四讲。这一章我打算把 /etc/init.d/rcS 这个脚本彻底讲透。很多做嵌入式开发的朋友,可能天天跟它打交道,但真要问它什么时候被调用、里面该写什么、不该写什么,未必能说清楚。我当年刚入行时也踩过坑,有一次把网络初始化写在了文件系统挂载之前,结果系统启动到一半就卡死了……嗯,从那以后我就养成了仔细读 rcS 的习惯。
4.1 rcS 到底是什么?
rcS 的全称是 Run Command Script,它是 Busybox init 进程启动后执行的第一个用户态脚本。说白了,它就是整个用户空间的“总调度员”。
它的核心职责就三条:
- 初始化基础环境:挂载虚拟文件系统、设置主机名、配置网络
- 启动核心服务:比如 syslogd、telnetd、dropbear(SSH)
- 执行用户自定义逻辑:加载驱动、启动应用程序
关键认知:rcS 不是内核的一部分,它是 init 进程 fork 出来的一个 shell 子进程。这意味着 rcS 里如果出现死循环或者阻塞调用,整个系统启动就会卡住。我见过不少产品因为 rcS 里放了一个 while true 的等待脚本,结果系统永远进不了 shell。
4.2 rcS 的执行时机
要理解 rcS 的执行时机,得先看 init 进程的启动流程。我画了一张图,你可以对照着看。
从这张图你能看到,rcS 是在 init 进程读取 /etc/inittab 后,匹配到 ::sysinit:/etc/init.d/rcS 这一行时被调用的。它属于 sysinit 动作,这意味着:
- rcS 在系统启动阶段最早被执行
- rcS 执行期间,其他
respawn、askfirst等动作不会被触发 - rcS 必须正常退出,init 才会继续处理后续的 inittab 条目
我曾经在一个项目中,把 rcS 脚本里放了一个 sleep 30,目的是等某个硬件就绪。结果整个系统启动慢了半分钟,客户直接投诉。后来我改成用 polling 加超时机制,才把启动时间压下来。记住:rcS 里不要做长时间的阻塞等待。
4.3 rcS 的基本结构
一个标准的 rcS 脚本,结构其实很清晰。我拿一个实际项目中的例子来说明:
#!/bin/sh
# /etc/init.d/rcS - 系统初始化脚本
# 版本: 1.2
# 日期: 2024-03-15
# 设置 PATH
PATH=/sbin:/bin:/usr/sbin:/usr/bin
export PATH
# 挂载虚拟文件系统
mount -t proc none /proc
mount -t sysfs none /sys
mount -t tmpfs none /tmp
mount -t devtmpfs none /dev
# 设置主机名
hostname -F /etc/hostname
# 配置网络(如果存在配置文件)
if [ -f /etc/network.conf ]; then
. /etc/network.conf
ifconfig eth0 $IP_ADDR netmask $NETMASK up
route add default gw $GATEWAY
fi
# 启动系统日志服务
syslogd
# 启动 SSH 服务(如果存在)
if [ -x /usr/sbin/dropbear ]; then
dropbear
fi
# 加载用户自定义驱动
for module in /lib/modules/*.ko; do
[ -f "$module" ] && insmod "$module"
done
# 启动主应用程序
if [ -x /usr/bin/myapp ]; then
/usr/bin/myapp &
fi
# 返回成功
exit 0
这个脚本的结构,我把它拆成几个逻辑段:
| 段落 | 作用 | 注意事项 |
|---|---|---|
| 环境准备 | 设置 PATH、导出环境变量 | PATH 要包含常用目录,否则后续命令可能找不到 |
| 虚拟文件系统挂载 | 挂载 proc、sysfs、tmpfs、devtmpfs | 顺序不能乱,先挂 proc 再挂 sysfs |
| 基础配置 | 主机名、网络、时区等 | 网络配置建议放在后面,避免依赖未就绪 |
| 服务启动 | syslogd、dropbear、telnetd 等 | 建议用 & 后台运行,不要阻塞 |
| 用户自定义 | 驱动加载、应用启动 | 注意检查文件是否存在,避免报错 |
| 退出 | exit 0 |
必须返回 0,否则 init 会认为初始化失败 |
4.4 我的一些经验总结
写 rcS 脚本这么多年,我总结了几条铁律:
- 先挂载,再做事:proc、sysfs、tmpfs 必须在最前面挂载,否则很多工具用不了。我见过有人把
mount -t proc写在后面,结果ps命令直接报错。 - 能用后台就用后台:服务类程序(syslogd、dropbear)加上
&,让它们后台运行。rcS 只负责“启动”,不负责“守护”。 - 检查文件存在性:用
[ -f ]或[ -x ]判断文件是否存在,避免脚本执行到一半报错退出。 - 不要写死路径:用变量代替硬编码路径,方便后期维护。
- 加注释:你想想看,三个月后你自己回来看这个脚本,没有注释的话,你可能都忘了当时为什么这么写。
我个人习惯在 rcS 脚本开头加一个 set -e,这样任何一条命令失败都会让脚本立即退出。但要注意,有些命令(比如 ifconfig 配置网络)可能因为硬件未就绪而失败,这时候 set -e 反而会坏事。所以我的做法是:关键步骤用 set -e,非关键步骤手动加 || true 忽略错误。
4.5 常见陷阱与避坑
这里列几个我实际踩过的坑:
- 陷阱一:rcS 里用
exit 1—— 如果 rcS 返回非零值,init 会认为初始化失败,有些 Busybox 版本会直接重启系统。所以 rcS 最后一定要exit 0。 - 陷阱二:忘记挂载
/dev—— 没有 devtmpfs 或 mdev,设备节点无法自动创建,很多驱动加载后会找不到设备文件。 - 陷阱三:网络配置放在最前面 —— 如果网络依赖某个驱动,而驱动加载在后面,网络配置就会失败。顺序很重要。
- 陷阱四:在 rcS 里启动前台进程 —— 前台进程会阻塞 rcS 的执行,导致后续的 inittab 条目无法被处理。所有长期运行的服务都应该后台启动。