有了这个脚本,再也不用担心设备重启后的配置丢失
1. 为什么重启后配置会“消失”?
你有没有遇到过这样的情况:
在树莓派、Orange Pi 或其他 ARM 开发板上,好不容易把 GPIO 引脚设成输出、把网络参数调好、把 USB 摄像头的权限配完,结果一重启——全没了。LED 不亮了,摄像头打不开,IP 地址又变回默认值。
这不是设备坏了,也不是你操作错了。
这是 Linux 启动机制在“按规矩办事”:它只加载内核和基础服务,不会主动记住你手动执行过的每一条命令。
比如你敲了这行:
echo "6" > /sys/class/gpio/export系统确实把 GPIO6 导出了,但/sys是内存中的虚拟文件系统(sysfs),断电即清空。重启后,所有通过echo写入的配置都会归零。
再比如你改了/etc/network/interfaces,加了一条静态 IP,但忘了让网络服务在开机时自动启用——那配置文件虽在,服务却不启动,IP 还是 DHCP 获取的。
所以问题本质不是“配置丢了”,而是配置没被纳入启动流程。
只要让这些操作变成“开机自动执行的任务”,一切就稳了。
2. 两种主流方案:init.d 和 systemd,选哪个?
Armbian 默认基于 Debian/Ubuntu,而现代 Debian 系统早已全面转向systemd作为 PID 1 进程(也就是系统第一个启动的程序)。你可以用这条命令确认:
ps -p 1 -o comm=如果输出是systemd,那就说明:你的设备底层就是 systemd 驱动的。
但 Armbian 为了兼容老用户和旧教程,仍然支持传统的/etc/init.d/脚本方式。也就是说,你写一个gpio-init.sh放进/etc/init.d/,再用update-rc.d注册,它也能跑起来——只不过,systemd 会在背后悄悄把它包装成一个临时 service 来运行。
那么该选哪条路?我们直接说结论:
新项目、新脚本,一律推荐 systemd unit 方式
不建议为新需求再写 init.d 脚本(除非维护遗留系统)
原因很实在:
- systemd 支持精确依赖控制(比如“等网络就绪后再运行我的脚本”)
- 启动过程并行化,比 init.d 串行执行快得多
- 日志统一归档,用
journalctl -u myscript.service就能查全部输出 - 错误自动捕获,失败时有明确状态提示,不像 init.d 脚本静默失败
下面我们就用一个真实可运行的案例,带你从零写出一个“开机自动恢复 GPIO 配置”的脚本,并确保它稳定可靠。
3. 动手实践:写一个真正可靠的开机启动脚本
3.1 明确目标与范围
我们要解决的不是“让 LED 亮起来”这个单一动作,而是构建一个可复用、可维护、可扩展的配置恢复机制。具体包括:
- 自动导出指定 GPIO 引脚(6、7、8、9、10)
- 设置方向(out/in)
- 设置初始电平(高/低)
- 兼容不同硬件平台(避免硬编码路径)
- 出错时有日志可查,不卡死启动流程
提示:不要把所有配置都塞进一个脚本里。GPIO 初始化、网络配置、USB 设备权限,应分拆为独立 service,便于单独启停和调试。
3.2 编写核心脚本文件
我们把实际操作逻辑放在一个独立的 shell 脚本中,路径定为/usr/local/bin/device-init.sh(放在这里符合 FHS 标准,且不易被系统更新覆盖):
#!/bin/bash # device-init.sh —— 设备级初始化脚本 # 支持:GPIO 配置、USB 权限修复、自定义网络准备等 set -e # 任一命令失败即退出 LOG_FILE="/var/log/device-init.log" exec >> "$LOG_FILE" 2>&1 echo "[$(date)] ===== Starting device init =====" # --- GPIO 配置区 --- GPIO_LIST=(6 7 8 9 10) for pin in "${GPIO_LIST[@]}"; do if [ ! -d "/sys/class/gpio/gpio${pin}" ]; then echo "Exporting GPIO${pin}" echo "${pin}" > /sys/class/gpio/export 2>/dev/null || true sleep 0.1 fi done # 设置方向与初始值 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/gpio6/value # 系统运行指示灯 echo "0" > /sys/class/gpio/gpio8/value # 默认关闭某功能引脚 echo "0" > /sys/class/gpio/gpio9/value echo "0" > /sys/class/gpio/gpio10/value # --- USB 设备权限修复(示例)--- if [ -c "/dev/video0" ]; then echo "Fixing video0 permissions" chmod 666 /dev/video0 fi echo "[$(date)] ===== Device init completed ====="保存后赋予执行权限:
sudo chmod +x /usr/local/bin/device-init.sh小技巧:脚本开头加
set -e,确保任意一步失败就停止,避免半截配置引发异常;重定向日志到/var/log/,方便后续排查。
3.3 创建 systemd service 单元文件
接下来,告诉 systemd:“这个脚本,我要你在开机时自动运行”。
创建 service 文件:
sudo nano /etc/systemd/system/device-init.service内容如下(注意注释已精简,生产环境建议保留):
[Unit] Description=Device initialization at boot Documentation=https://github.com/yourname/device-init After=multi-user.target Wants=multi-user.target [Service] Type=oneshot ExecStart=/usr/local/bin/device-init.sh RemainAfterExit=yes User=root StandardOutput=journal StandardError=journal [Install] WantedBy=multi-user.target关键点说明:
Type=oneshot:表示这是一个一次性执行的脚本,不是长期运行的服务RemainAfterExit=yes:即使脚本执行完了,systemd 也认为该 service 处于“active”状态,方便依赖管理User=root:必须以 root 权限运行,否则无法写入/sys/class/gpio/StandardOutput=journal:所有echo输出都会进入 journal 日志,可用journalctl查看
启用并测试:
# 重新加载 systemd 配置 sudo systemctl daemon-reload # 启用开机自启 sudo systemctl enable device-init.service # 立即运行一次(不重启也可验证) sudo systemctl start device-init.service # 查看运行状态和日志 sudo systemctl status device-init.service sudo journalctl -u device-init.service -n 20 --no-pager如果看到active (exited)和日志末尾有Device init completed,说明一切正常。
4. 常见问题与避坑指南
4.1 “脚本执行了,但 GPIO 没生效”
最常见原因是:脚本执行时机太早,GPIO 子系统还没就绪。
比如/sys/class/gpio/目录在 kernel 加载 gpiochip 驱动后才出现。如果你的脚本在multi-user.target之前就运行,会因路径不存在而失败。
解决方案:在[Unit]中添加更精确的依赖:
After=sysinit.target Wants=sysinit.target或者更稳妥地,加一行检测逻辑到脚本开头:
while [ ! -d "/sys/class/gpio" ]; do echo "Waiting for sysfs GPIO..." sleep 0.5 done4.2 “重启后日志里有 Permission denied”
错误类似:
Failed to write '6' to '/sys/class/gpio/export': Permission denied这是因为某些平台(如较新内核的 Armbian)默认禁用了 legacy sysfs GPIO 接口,转而使用libgpiod工具链。
解决方案有两个:
- 短期兼容:在
/boot/armbianEnv.txt中添加overlays=gpio-sysfs并重启(适用于旧版驱动) - 长期推荐:改用
gpioset命令重写脚本(需安装gpiod包):
sudo apt install gpiod # 示例:设置 GPIO6 为高电平 gpioset $(gpiofind "GPIO6")=1注意:
gpiofind名称取决于设备树中定义的 label,不是所有板子都预设 label,此时仍需 fallback 到 sysfs。
4.3 “脚本运行了,但 LED 一闪就灭”
这往往是因为:脚本执行完后,其他服务(如某个用户空间程序或桌面环境)又把引脚重置了。
排查方法:
# 查看谁在操作 GPIO6 sudo lsof /sys/class/gpio/gpio6/* # 或监控写入行为 sudo auditctl -w /sys/class/gpio/gpio6/value -p w长期解法:将关键引脚的控制权“锁定”,例如在 service 中加入:
# 防止被其他进程覆盖(仅限支持的内核) echo "6" > /sys/class/gpio/gpio6/active_low echo "1" > /sys/class/gpio/gpio6/direction更彻底的方式是:把初始化逻辑封装进内核模块或设备树 overlay,但这已超出脚本范畴,属于深度定制。
5. 进阶建议:让配置真正“持久化”
上面的脚本解决了“开机执行”,但还有两个隐藏痛点:
- 配置参数写死在脚本里,改个引脚号要编辑代码
- 多台设备共用同一镜像,但 GPIO 用途各不相同
推荐做法:引入外部配置文件
新建/etc/device-init/config.yaml(需先安装yq工具):
gpio: - pin: 6 direction: out value: 1 label: "system_led" - pin: 8 direction: out value: 0 label: "relay_ctrl" usb: - device: "/dev/video0" mode: "0666"然后修改device-init.sh,用yq读取配置动态执行。这样,不同设备只需替换 config.yaml,无需改动脚本本身。
提示:
yq安装命令为sudo snap install yq或sudo apt install python3-yaml && pip3 install yq(根据系统选择)。
6. 总结:一套脚本,三种保障
我们完成的不是一个“点灯脚本”,而是一套面向嵌入式场景的配置持久化工程实践:
第一层保障:机制可靠
使用 systemd service 替代 init.d,获得依赖管理、日志追踪、状态反馈等原生能力。第二层保障:执行稳健
脚本内置路径检测、错误捕获、日志记录,不因单点失败导致整机启动异常。第三层保障:维护友好
配置与逻辑分离,支持多设备差异化部署,升级时只需替换配置文件。
最后提醒一句:
不要追求“一次写完,永远不用管”。真正的稳定性,来自清晰的结构、可验证的步骤、以及每次重启后都能快速定位问题的能力。
你现在拥有的,不只是一个脚本,而是一个可复制、可演进、可交付的嵌入式初始化范式。
获取更多AI镜像
想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。