想让设备一通电就工作?试试这个开机启动方案
在嵌入式开发或边缘计算场景中,我们常常希望设备一上电就能自动运行某些关键任务——比如点亮状态灯、初始化GPIO引脚、启动监控程序或者加载传感器配置。这种“自启动”能力看似简单,但背后涉及Linux系统启动机制的核心逻辑。
如果你用的是Armbian这类基于Debian/Ubuntu的系统,虽然它支持传统的init.d脚本方式,但真正的主角其实是现代的systemd。理解这一点,才能写出稳定、可控、可维护的开机启动方案。
本文将带你从零开始,掌握如何在真实环境中配置一个可靠的开机启动脚本,并解释为什么推荐使用systemd service而非老式的init.d方法。
1. 理解Linux启动机制:init.d vs systemd
1.1 init.d(SysV init)——传统方式
init.d是早期Linux系统的标准启动管理方式,它的核心逻辑非常直接:
- 所有启动脚本放在
/etc/init.d/目录下 - 系统根据运行级别(runlevel),在
/etc/rcX.d/目录中创建指向这些脚本的符号链接 - 链接名以
Sxx开头(如S01script),数字决定执行顺序 - 脚本自己实现
start、stop、restart等功能
优点是结构简单,容易理解;缺点也很明显:
- 不支持并行启动,拖慢整体开机速度
- 无法定义服务依赖关系(比如必须先联网再启动上传程序)
- 日志分散,调试困难
- 缺乏自动重启、健康检查等高级功能
1.2 systemd —— 现代系统的标准
systemd是目前主流Linux发行版默认的初始化系统(PID 1进程),它通过“单元文件”(unit files)来描述和管理系统服务。
每个服务由一个.service文件定义,通常位于/etc/systemd/system/或/usr/lib/systemd/system/。
相比init.d,systemd的优势非常明显:
| 特性 | systemd 支持 | init.d |
|---|---|---|
| 并行启动 | ✅ | ❌ |
| 服务依赖管理 | ✅(After=, Requires=) | ❌ |
| 自动重启策略 | ✅(Restart=on-failure) | ❌ |
| 内建日志查看 | ✅(journalctl) | ❌ |
| 状态监控与超时控制 | ✅ | ❌ |
更重要的是,即使你写了init.d脚本,Armbian这类系统也会通过兼容层将其封装成临时的systemd单元来运行。也就是说,systemd 才是真正的调度者。
2. Armbian中的启动体系真相
Armbian 基于 Debian 或 Ubuntu,因此默认使用systemd作为 init 系统。
你可以用下面这条命令验证:
ps -p 1 -o comm=输出结果应该是:
systemd这说明内核启动后的第一个进程就是systemd,它是整个系统服务的“总指挥”。
即便你在/etc/init.d/下放了一个脚本,并用update-rc.d注册为开机启动项,实际执行过程依然是:
systemd检测到存在 SysV 兼容脚本- 动态生成一个临时的
.service单元文件 - 按照
systemd的规则去调用该脚本
所以本质上,所有启动行为最终都归systemd管理。
3. 如何查看当前的开机启动项?
了解系统现状是优化的第一步。以下是几个实用命令,帮助你全面掌握哪些服务会在开机时被拉起。
3.1 查看所有启用的 systemd 服务
systemctl list-unit-files --type=service --state=enabled这条命令会列出所有设置为“开机自启”的服务,例如:
ssh.service enabled cron.service enabled networking.service enabled docker.service enabled your-custom-script.service enabled这些都是真正意义上的“开机启动项”。
3.2 查看正在运行的服务
systemctl --type=service --state=running可以快速确认哪些服务已经成功启动。
3.3 查看 init.d 脚本注册情况
如果你曾经使用过update-rc.d添加脚本,可以通过以下命令查看:
ls /etc/rc*.d/你会看到类似这样的输出:
/etc/rc0.d/K01gpio-init.sh /etc/rc2.d/S01gpio-init.sh /etc/rc6.d/K01gpio-init.sh其中:
Sxx表示 Start,开机时执行Kxx表示 Kill,关机时执行- 数字代表执行顺序
但这只是历史遗留痕迹,背后仍是systemd在调度。
3.4 查看多用户模式下的依赖树
systemctl list-dependencies multi-user.target这个命令能展示系统进入正常工作状态时需要加载的所有服务,有助于理解启动流程。
4. 推荐做法:使用 systemd service 实现开机启动
既然systemd是底层核心,那我们就应该直接使用它的原生方式——编写.service文件,这才是最规范、最可靠的做法。
下面我们以一个常见的需求为例:设备上电后自动初始化GPIO引脚并点亮LED指示灯。
4.1 编写实际执行脚本
首先创建一个可执行脚本,用于完成具体的初始化操作。
sudo nano /usr/local/bin/gpio-init.sh内容如下:
#!/bin/bash # 导出 GPIO 引脚 echo 6 > /sys/class/gpio/export 2>/dev/null echo 7 > /sys/class/gpio/export 2>/dev/null echo 8 > /sys/class/gpio/export 2>/dev/null echo 9 > /sys/class/gpio/export 2>/dev/null echo 10 > /sys/class/gpio/export 2>/dev/null # 设置方向 echo out > /sys/class/gpio/gpio6/direction echo in > /sys/class/gpio/gpio7/direction echo out > /sys/class/gpio/gpio8/direction echo out > /sys/class/gpio/gpio9/direction echo out > /sys/class/gpio/gpio10/direction # 设置初始输出值(高电平) echo 1 > /sys/class/gpio/gpio8/value echo 1 > /sys/class/gpio/gpio9/value echo 1 > /sys/class/gpio/gpio10/value # 点亮系统运行指示灯(假设GPIO6接LED) echo 1 > /sys/class/gpio/gpio6/value保存后赋予执行权限:
sudo chmod +x /usr/local/bin/gpio-init.sh⚠️ 注意:部分GPIO可能已被占用或导出失败,建议添加
2>/dev/null忽略错误,避免阻塞启动。
4.2 创建 systemd 服务文件
接下来创建对应的 service 文件:
sudo nano /etc/systemd/system/gpio-init.service填入以下内容:
[Unit] Description=Initialize GPIO pins on boot After=multi-user.target # 可选:如果依赖网络,可加上 After=network.target [Service] Type=oneshot ExecStart=/usr/local/bin/gpio-init.sh RemainAfterExit=yes [Install] WantedBy=multi-user.target各字段含义说明:
Description: 服务描述,便于识别After: 指定启动时机,确保在基础系统准备好之后运行Type=oneshot: 表示这是一个一次性任务,执行完即结束RemainAfterExit=yes: 即使脚本退出,也认为服务处于“激活”状态WantedBy=multi-user.target: 加入多用户目标,随系统启动
4.3 启用并测试服务
启用服务,使其开机自启:
sudo systemctl daemon-reload sudo systemctl enable gpio-init.service立即手动运行一次测试:
sudo systemctl start gpio-init.service查看执行状态:
sudo systemctl status gpio-init.service如果一切正常,你应该能看到类似输出:
● gpio-init.service - Initialize GPIO pins on boot Loaded: loaded (/etc/systemd/system/gpio-init.service; enabled) Active: active (exited) since Mon 2025-04-05 10:00:00 UTC; 5s ago还可以通过journalctl查看详细日志:
sudo journalctl -u gpio-init.service -b5. 为什么不推荐使用 init.d 脚本?
尽管/etc/init.d/仍然可用,但我们不建议新项目采用这种方式,原因如下:
- 已被时代淘汰:主流发行版已全面转向
systemd - 缺乏精细控制:无法设置超时、重试、依赖等策略
- 调试困难:日志分散,难以追踪问题
- 兼容性风险:未来系统可能完全移除 SysV 支持
- 执行顺序不可靠:仅靠命名排序,容易出错
相比之下,systemd提供了更强大、更安全、更易维护的机制。
6. 常见问题与最佳实践
6.1 脚本没执行?检查这几个点
- 是否执行了
systemctl daemon-reload? - 脚本是否有可执行权限?
chmod +x - 路径是否正确?建议使用绝对路径
- 是否因错误退出导致 systemd 认为失败?可在脚本开头加
set -e或捕获异常 - 是否依赖其他服务未就绪?如需网络,请添加
After=network.target
6.2 如何延迟执行?
有时需要等待硬件稳定或系统服务准备完毕,可以在 service 文件中加入:
[Service] Type=oneshot ExecStartPre=/bin/sleep 3 ExecStart=/usr/local/bin/your-script.sh表示先等待3秒再执行主命令。
6.3 如何让脚本持续运行?
如果你要启动一个常驻进程(如监控程序),应改为:
[Service] Type=simple ExecStart=/usr/local/bin/monitor.py Restart=always RestartSec=5这样即使崩溃也会自动重启。
6.4 安全建议
- 尽量不要在 root 权限下长期运行脚本
- 使用专用用户运行服务:
User=nobody - 避免在启动脚本中阻塞太久,影响系统响应
- 关键操作加日志输出,方便排查
7. 总结
想让设备一通电就开始工作,关键在于掌握现代Linux的启动机制。在Armbian这类系统中,虽然仍支持老旧的init.d脚本,但真正的控制权掌握在systemd手中。
我们推荐的做法是:
绕过兼容层,直接使用
.service文件定义开机任务
这样做不仅能获得更好的控制力和稳定性,还能利用systemd提供的日志、依赖、重启等高级功能,大幅提升系统的健壮性和可维护性。
无论是初始化GPIO、启动守护进程,还是加载配置脚本,都可以通过标准化的systemd service方式优雅实现。
获取更多AI镜像
想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。