快速上手systemd服务配置,打造专属开机启动脚本
1. 引言:为什么选择 systemd 配置开机启动?
在现代 Linux 系统中,systemd已成为默认的初始化系统(init system),取代了传统的 SysV init 和init.d脚本。尤其在基于 Debian/Ubuntu 的嵌入式系统如 Armbian 中,虽然仍保留对/etc/init.d/脚本的兼容性,但底层实际由systemd 统一调度管理。
这意味着:
- 即使你使用
update-rc.d注册了一个init.d脚本,最终也是通过 systemd 动态生成的 unit 来执行。 - 直接编写
.serviceunit 文件,可以获得更精细的控制能力:依赖管理、并行启动、自动重启策略、日志追踪等。
本文将带你从零开始,创建一个可复用的 systemd 服务单元,实现自定义脚本的开机自动运行,并提供完整的调试与验证方法,适用于 GPIO 控制、环境初始化、守护进程启动等场景。
2. 核心概念解析:理解 systemd 与 init.d 的关系
2.1 systemd 是什么?
systemd 是 Linux 系统的系统和服务管理器,作为 PID 1 进程启动,负责:
- 挂载文件系统
- 启动网络服务
- 加载设备驱动
- 执行用户定义的服务
它以unit 文件(如.service,.target,.socket)描述资源及其行为,所有操作都围绕这些声明式配置展开。
2.2 init.d 的历史角色
传统 SysV init 使用/etc/init.d/目录下的 shell 脚本来管理服务,配合/etc/rc?.d/中的符号链接控制启动顺序(如S01xxx,S02xxx)。其缺点包括:
- 启动是串行的,效率低
- 缺乏依赖表达机制
- 日志分散,难以排查问题
2.3 实际运行机制:Armbian 如何处理 init.d 脚本?
尽管你可以写一个/etc/init.d/my-script.sh并用update-rc.d my-script defaults注册,但在 Armbian 这类 systemd 系统中:
- systemd 会检测到该脚本被启用
- 自动生成一个临时 unit 文件(如
my-script.service) - 将其纳入
multi-user.target依赖链 - 实际启动仍由 systemd 调度和监控
可通过以下命令验证:
ps -p 1 -o comm=输出为:
systemd说明系统真正的“主控者”是 systemd。
3. 创建 systemd 服务:完整实践步骤
我们将以一个典型的 GPIO 初始化脚本为例,演示如何将其封装为 systemd service。
3.1 准备启动脚本
首先创建实际要执行的脚本文件。建议放置在/usr/local/bin/或/opt/下,避免与系统脚本冲突。
sudo nano /usr/local/bin/gpio-init.sh写入如下内容(根据硬件调整 GPIO 编号):
#!/bin/bash # 导出 GPIO 引脚 echo "6" > /sys/class/gpio/export 2>/dev/null || true echo "7" > /sys/class/gpio/export 2>/dev/null || true echo "8" > /sys/class/gpio/export 2>/dev/null || true echo "9" > /sys/class/gpio/export 2>/dev/null || true echo "10" > /sys/class/gpio/export 2>/dev/null || true # 设置方向 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 # 设置初始电平(高电平点亮 LED) echo "1" > /sys/class/gpio/gpio8/value echo "1" > /sys/class/gpio/gpio9/value echo "1" > /sys/class/gpio/gpio10/value # 点亮状态指示灯 echo "1" > /sys/class/gpio/gpio6/value保存后赋予可执行权限:
sudo chmod +x /usr/local/bin/gpio-init.sh⚠️ 注意:
2>/dev/null || true可防止重复导出时报错导致脚本中断。
3.2 创建 systemd unit 文件
创建服务定义文件:
sudo nano /etc/systemd/system/gpio-init.service填入以下内容:
[Unit] Description=GPIO Initialization Service After=multi-user.target # 可选:如果依赖网络 # After=network-online.target # Requires=network-online.target [Service] Type=oneshot ExecStart=/usr/local/bin/gpio-init.sh RemainAfterExit=yes StandardOutput=journal StandardError=journal [Install] WantedBy=multi-user.target参数详解:
| 字段 | 说明 |
|---|---|
Description | 服务描述,便于识别 |
After=multi-user.target | 表示在多用户模式之后运行,确保基础系统已就绪 |
Type=oneshot | 表示此服务只运行一次,不常驻后台 |
RemainAfterExit=yes | 告诉 systemd 脚本结束后服务仍视为“激活”状态 |
StandardOutput/StandardError=journal | 输出重定向至 journal 日志系统 |
WantedBy=multi-user.target | 启用时加入 multi-user.target 的依赖 |
3.3 启用并启动服务
刷新 systemd 配置,加载新 unit:
sudo systemctl daemon-reexec 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 - GPIO Initialization Service Loaded: loaded (/etc/systemd/system/gpio-init.service; enabled) Active: active (exited) since ...若出现failed,可通过日志排查:
journalctl -u gpio-init.service --since "5 minutes ago"4. 查看与管理系统启动项
4.1 列出所有启用的 systemd 服务
查看哪些服务会在开机时自动启动:
systemctl list-unit-files --type=service --state=enabled筛选特定服务:
systemctl list-unit-files --type=service | grep gpio4.2 查看当前正在运行的服务
systemctl --type=service --state=running4.3 查看 init.d 兼容脚本的注册情况
如果你曾使用update-rc.d添加过脚本,可用以下命令查看:
ls /etc/rc*.d/ | grep -i your-script-name例如:
ls /etc/rc*.d/ | grep gpio输出可能为:
/etc/rc2.d/S01gpio-init.sh /etc/rc3.d/S01gpio-init.sh ...但这只是兼容层痕迹,真正控制权仍在 systemd。
4.4 查看启动依赖树
查看 multi-user.target 包含的所有服务:
systemctl list-dependencies multi-user.target可加--all显示完整依赖链:
systemctl list-dependencies multi-user.target --all5. 常见问题与优化建议
5.1 脚本未生效?检查要点
| 问题 | 检查方式 | 解决方案 |
|---|---|---|
| 脚本无执行权限 | ls -l /usr/local/bin/gpio-init.sh | chmod +x |
| 路径错误 | cat /etc/systemd/system/gpio-init.service | 确保ExecStart=路径正确 |
| GPIO 设备未就绪 | journalctl -u gpio-init.service | 添加After=sysfs.target或延迟 |
| 权限不足 | 日志显示 Permission denied | 使用 root 权限运行(默认即如此) |
5.2 添加延迟或等待条件(进阶)
某些情况下,内核尚未准备好 GPIO 子系统,可在[Unit]段增加等待:
[Unit] Description=GPIO Initialization Service After=multi-user.target sysfs.target # 延迟 2 秒确保设备节点存在(谨慎使用) # ExecStartPre=/bin/sleep 2或者使用 udev 触发更精确的时机(适合复杂场景)。
5.3 最佳实践总结
优先使用 systemd 而非 init.d
更高效、可控、易于维护。脚本路径推荐
/usr/local/bin/
避免与系统目录混淆,且通常位于 PATH 中。启用日志记录(journal)
便于后期排错,无需额外重定向输出。避免阻塞式操作
Type=oneshot不应长时间循环,否则影响启动流程。命名清晰,描述明确
如backup-data.service,wifi-hotspot.service,提升可读性。
6. 总结
通过本文,你应该已经掌握了如何在 Armbian 或其他基于 systemd 的 Linux 系统中:
- ✅ 理解 systemd 与 init.d 的本质区别
- ✅ 编写可执行的初始化脚本
- ✅ 创建标准的
.serviceunit 文件 - ✅ 启用并调试开机启动服务
- ✅ 使用
systemctl和journalctl进行状态监控
相比老旧的init.d方式,直接使用 systemd 不仅能获得更好的性能和可靠性,还能充分利用现代 Linux 的服务管理体系。
核心结论:
在 systemd 主导的时代,直接编写 .service 文件是最佳实践。init.d 脚本只是向后兼容的过渡方案,不应作为新项目的首选。
获取更多AI镜像
想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。