从0开始学开机启动脚本,测试镜像保姆级入门教程
你是不是也遇到过这样的问题:刚部署好一个嵌入式Linux系统,想让某个服务一开机就自动运行,结果改了配置却没生效?或者在调试过程中反复重启,每次都要手动敲命令?别急,这篇教程就是为你准备的——不讲抽象理论,不堆专业术语,只用最直白的语言,带你从零开始,亲手写一个能真正跑起来的开机启动脚本。
本文基于“测试开机启动脚本”这个轻量级镜像环境,全程在真实可操作的上下文中展开。无论你是刚接触嵌入式开发的新手,还是想快速验证启动逻辑的工程师,都能跟着一步步完成。不需要提前掌握init系统原理,也不用翻手册查参数,所有操作都以“能看见结果”为第一标准。
我们不追求一步到位的完美方案,而是先让你看到脚本能动起来,再逐步理解它为什么能动、怎么让它更稳、哪些地方容易踩坑。整个过程就像教朋友搭积木:先拼出形状,再看结构,最后自己设计新造型。
1. 先搞清楚:开机时到底发生了什么
很多新手卡在第一步,不是不会写脚本,而是不知道该往哪儿放。其实Linux启动过程比想象中简单,就像一条流水线,每个环节只做一件事,而且顺序固定。
在本镜像中,启动流程非常清晰:
linuxrc (指向 busybox 的软连接) ↓ /etc/inittab ↓ /etc/init.d/rcS ↓ /etc/init.d/Sxx 脚本(按字母顺序执行)这里没有systemd,没有复杂的依赖管理,就是一个精简可靠的busybox init链路。你可以把它想象成一个老式收音机的开关顺序:按下总电源(linuxrc),它自动拨到预设频道(inittab),然后依次播放预存节目(rcS → Sxx)。
linuxrc是系统启动后第一个运行的程序,它本质就是 busybox 的一个软链接,负责拉起整个初始化框架;/etc/inittab是它的“操作清单”,告诉系统接下来该做什么;/etc/init.d/rcS是一个特殊脚本,类似“开场白”,所有通用初始化动作都放在这里;/etc/init.d/Sxx是真正的“任务执行者”,文件名以S开头 + 两位数字(如S10network),数字越小越早执行。
记住这个链条就够了。后面所有操作,都是在这条线上“插”进你的脚本。
2. 四种方法实测:哪个最简单、哪个最可靠
镜像文档里提到四种常见方式,我们不空谈,直接挨个试一遍,每种都给出完整命令、预期效果和一句话判断依据。
2.1 方法一:直接改 inittab(最快上手)
这是最快看到效果的方式,适合临时验证或极简场景。
打开/etc/inittab文件:
vi /etc/inittab找到类似这一行(通常在末尾):
::sysinit:/etc/init.d/rcS在它下面新增一行:
::once:/bin/sh -c 'echo "Hello from inittab!" > /tmp/startup.log'保存退出后,重启系统(或执行exec /sbin/init模拟重载)。
验证方式:
cat /tmp/startup.log # 输出:Hello from inittab!✔ 优点:改一行就生效,无需写独立脚本
注意:只适合单条命令;不能用&后台运行,否则会阻塞后续启动
2.2 方法二:追加到 rcS(推荐新手首选)
rcS是系统默认的初始化入口,修改它相当于在“开场白”里加一句台词,既安全又直观。
编辑/etc/init.d/rcS:
vi /etc/init.d/rcS在文件末尾添加:
# 自定义启动任务 echo "Running custom task in rcS..." >> /tmp/rcS.log /bin/sh -c 'sleep 2; echo "rcS task done at $(date)" >> /tmp/rcS.log' &重启后检查:
cat /tmp/rcS.log # 输出包含两行:提示语 + 时间戳✔ 优点:支持多行逻辑、可调用其他脚本、不影响原有流程
注意:不要在这里放阻塞型长任务(比如死循环),否则系统卡住
2.3 方法三:新建 Sxx 脚本(生产环境首选)
这是最规范的做法,也是嵌入式设备普遍采用的方式。它把你的任务变成“标准插件”,便于管理、启停和复用。
创建脚本文件:
vi /etc/init.d/S99mytest内容如下(注意开头必须是#!/bin/sh):
#!/bin/sh # S99mytest - 自定义开机任务示例 case "$1" in start) echo "Starting mytest service..." echo "My test script is running at $(date)" > /tmp/mytest.log ;; stop) echo "Stopping mytest service..." rm -f /tmp/mytest.log ;; restart) $0 stop $0 start ;; *) echo "Usage: $0 {start|stop|restart}" exit 1 esac赋予执行权限:
chmod +x /etc/init.d/S99mytest重启后验证:
cat /tmp/mytest.log # 输出时间戳信息✔ 优点:支持 start/stop/restart 控制;可被其他脚本依赖;符合 BusyBox init 规范
注意:文件名必须以S开头 + 两位数字;数字决定执行顺序(S10早于S99)
2.4 方法四:误用 /etc/profile?千万别!
很多人看到“开机执行”,第一反应是往/etc/profile里加命令。但请一定记住:
/etc/profile只在用户登录 Shell 时执行,不是开机时执行。
如果系统无人登录(比如纯后台服务设备),这里面的命令永远不会运行。
你可以做个实验:
# 在 /etc/profile 最后加一行 echo "profile executed" >> /tmp/profile.log然后不登录,直接重启。你会发现/tmp/profile.log根本不存在。
❌ 结论:这不是开机启动方案,而是“用户首次登录时的环境设置方案”。
正确用途:设置PATH、PS1等终端变量,让每次登录都有统一体验。
3. 动手写一个真正有用的启动脚本
光看例子不过瘾?我们来写一个实际能用的小工具:开机自动检测网络连通性,并记录状态。
3.1 创建脚本文件
vi /etc/init.d/S98netcheck输入以下内容:
#!/bin/sh # S98netcheck - 开机网络连通性检测 NET_LOG="/tmp/netcheck.log" TARGET="8.8.8.8" case "$1" in start) echo "[$(date)] Checking network..." >> $NET_LOG if ping -c 1 -W 2 $TARGET >/dev/null 2>&1; then echo "[$(date)] Network OK" >> $NET_LOG # 这里可以启动你的主服务,比如:/usr/bin/myapp & else echo "[$(date)] Network FAIL" >> $NET_LOG # 这里可以触发告警或降级逻辑 fi ;; stop) echo "[$(date)] Net check stopped" >> $NET_LOG ;; *) echo "Usage: $0 {start|stop}" exit 1 esac3.2 设置权限并测试
chmod +x /etc/init.d/S98netcheck # 手动运行一次测试 /etc/init.d/S98netcheck start cat /tmp/netcheck.log你会看到类似输出:
[Wed Apr 10 10:22:34 UTC 2024] Checking network... [Wed Apr 10 10:22:34 UTC 2024] Network OK3.3 关键细节说明
ping -c 1 -W 2表示只发1个包,超时2秒,避免卡住启动流程;- 所有日志都写入
/tmp/,因为它是内存文件系统,速度快且重启清空; - 脚本结构遵循标准
case "$1"模式,未来可轻松集成到其他管理工具中; - 如果你要启动后台服务,请确保加上
&并考虑 PID 管理(本例暂不展开)。
4. 常见问题与避坑指南
即使照着做,也可能遇到“明明改了却没反应”的情况。以下是我们在真实镜像中高频遇到的5个问题,附带一键排查法。
4.1 脚本写了但完全没执行?
快速检查三步:
ls -l /etc/init.d/S99*—— 确认文件存在且权限为-rwxr-xr-x;head -1 /etc/init.d/S99mytest—— 确认首行是#!/bin/sh(不是#!/bin/bash,busybox 不支持);sh -n /etc/init.d/S99mytest—— 语法检查,无报错才继续。
4.2 日志文件为空或没生成?
大概率是路径问题:
/tmp/是安全的,但/var/log/在某些镜像中可能不可写;- 不要用相对路径,全部用绝对路径(如
/tmp/test.log,而非./test.log); - 检查磁盘空间:
df -h,/tmp满了也会写失败。
4.3 脚本执行了,但后台服务没起来?
常见原因:
- 忘记加
&导致阻塞(如/usr/bin/myapp &); - 后台进程依赖环境变量,需在脚本中显式导出(如
export PATH=/usr/bin:/bin); - 使用
nohup更稳妥:nohup /usr/bin/myapp > /tmp/app.log 2>&1 &。
4.4 多个 Sxx 脚本执行顺序混乱?
数字决定一切:
S10first一定在S20second之前;- 如果两个脚本需要严格先后,就用不同数字,不要试图用
sleep协调; - 查看当前所有启动脚本顺序:
ls /etc/init.d/S*。
4.5 修改后不想重启,怎么热加载?
BusyBox init 支持信号重载:
kill -HUP 1 # 或者更明确地: kill -USR2 1 # 通知 init 重新读取 inittab注意:此操作仅重载
inittab和rcS,新添加的Sxx脚本仍需重启生效。
5. 总结:选对方法,少走三年弯路
回顾一下,我们做了什么:
- 看懂了启动链条:
linuxrc → inittab → rcS → Sxx,不再盲目乱改; - 实测了四种方法:
inittab快速验证、rcS简单可靠、Sxx规范可控、profile明确排除; - 写了一个带逻辑判断的真实脚本:网络检测 + 日志记录;
- 整理了5个高频问题的一键排查法,覆盖90%新手卡点。
如果你刚入门,建议从方法二(改 rcS)开始,改完立刻见效,建立信心;
如果要做长期项目,务必用方法三(Sxx 脚本),它让你的代码可维护、可交接、可升级;
永远记住:开机启动 ≠ 登录执行,别再往/etc/profile里塞启动命令。
最后送你一句经验之谈:
在嵌入式世界里,最可靠的不是最炫的方案,而是你能看懂、能修改、能解释给同事听的那个。
现在,关掉这篇教程,打开你的终端,试着写一个属于自己的S99hello吧。运行成功那一刻的提示符,比任何文档都更真实。
获取更多AI镜像
想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。