systemd工控服务开发:常驻程序、开机自启、异常自动重启、多服务依赖管理
systemd工控服务开发:常驻程序、开机自启、异常自动重启、多服务依赖管理
init.d那套启动脚本像手工裁缝——每个服务一个脚本,写法各不相同,出了问题得翻壳脚本找bug。systemd像流水线工厂——统一格式、统一管理、日志自带、依赖编排。工控时代,init.d该退休了。
一、systemd简介与工控优势
systemd从2015年开始统治Linux发行版的init系统,取代了古老的SysV init.d。对工控开发来说,systemd带来的不只是"启动快了"——它解决了工控场景最头疼的几个问题:
| 维度 | init.d | systemd |
|---|---|---|
| 服务管理 | 每个服务独立Shell脚本,写法混乱 | 统一Unit文件格式,标准化 |
| 日志 | 需要自己管日志文件 | journalctl内置日志,自动收集 |
| 依赖关系 | 手写脚本里加sleep等依赖 | After/Requires声明式依赖 |
| 异常重启 | 需要自己写守护脚本 | Restart=always一行配置搞定 |
| 启动并行 | 串行启动,慢 | 按依赖关系并行启动,快 |
| 资源限制 | 手动配置 | CPUAffinity/MemoryLimit原生支持 |
一句话总结:init.d需要你写运维脚本,systemd用配置文件替代运维脚本。工控要的是稳定和自动化,systemd刚好对症。
二、Unit文件编写详解
systemd用Unit文件描述服务,后缀为.service,存放路径:
/etc/systemd/system/— 管理员自定义服务(工控用这个)/lib/systemd/system/— 软件包安装的服务(别改这里的)
Unit文件分三段,每段有明确职责:
2.1 [Unit]段 — 描述与依赖
[Unit] Description=工业数据采集服务 Documentation=https://wiki.company.com/data-collector After=network.target serial-port.service Requires=serial-port.service Wants=time-sync.serviceAfter=:启动顺序,当前服务在这些服务之后启动(不保证它们已就绪)Requires=:强依赖,依赖服务失败时当前服务也启动失败Wants=:弱依赖,依赖服务失败时当前服务仍尝试启动
工控常见依赖链:网络就绪 → 串口服务就绪 → 数据采集启动。After管顺序,Requires管成败,两者配合才能确保"前置条件满足后才干活"。
2.2 [Service]段 — 运行配置
[Service] Type=simple ExecStart=/opt/app/data_collector --config /etc/app/config.ini ExecStop=/bin/kill -SIGTERM $MAINPID Restart=always RestartSec=5s StartLimitBurst=5 StartLimitIntervalSec=60 WorkingDirectory=/opt/app Environment=CONFIG_PATH=/etc/app/config.ini Environment=LOG_LEVEL=INFO StandardOutput=journal StandardError=journal逐项解释:
| 字段 | 含义 | 工控常用值 |
|---|---|---|
| Type | 进程启动类型 | simple(前台运行)/ forking(fork后父进程退出) |
| ExecStart | 启动命令 | 绝对路径 + 参数 |
| ExecStop | 停止命令 | 默认发送SIGTERM |
| Restart | 重启策略 | always(总是重启)/ on-failure(仅失败时) |
| RestartSec | 重启间隔 | 5s(避免立即重启导致资源竞争) |
| StartLimitBurst | 短时间内最大重启次数 | 5(防无限重启循环) |
| StartLimitIntervalSec | 短时间窗口 | 60s |
| WorkingDirectory | 工作目录 | 业务程序目录 |
| Environment | 环境变量 | 配置路径、日志级别等 |
2.3 [Install]段 — 安装与自启
[Install] WantedBy=multi-user.targetWantedBy=multi-user.target表示当系统进入多用户模式(正常运行模式)时启动此服务。systemctl enable就是把这个Unit文件链接到multi-user.target.wants/目录下,实现开机自启。
三、常驻程序配置
工控程序分两种运行方式:
3.1 Type=simple — 前台运行(推荐)
程序不fork,直接在前台运行,systemd认为ExecStart进程就是主进程。这是最简单最可靠的方式:
[Service] Type=simple ExecStart=/opt/app/data_collector程序代码不需要做daemon化处理——不用fork、不用setsid、不用关闭标准输出。systemd会替你管理所有这些。
3.2 Type=forking — 传统daemon方式
程序启动后fork出子进程运行,父进程退出。systemd通过PIDFile或cgroup追踪子进程:
[Service] Type=forking PIDFile=/var/run/data_collector.pid ExecStart=/opt/app/data_collector --daemonize这种方式容易出问题:PIDFile写入时机不对、cgroup追踪不准确。工控场景优先用simple,让程序保持前台运行。
四、开机自启配置
# 使服务开机自启(创建符号链接)sudosystemctlenable># 取消开机自启(删除符号链接)sudosystemctl disable># 立即启动服务(不等重启)sudosystemctl start># 查看服务状态sudosystemctl status># 查看是否已启用自启sudosystemctl is-enabled>五、异常自动重启这是systemd对工控最友好的特性之一——程序崩溃自动重启,一行配置替代之前整个守护脚本:
[Service] Restart=always # 无论什么退出原因都重启 RestartSec=5s # 重启前等待5秒,给系统缓冲时间 StartLimitBurst=5 # 60秒内最多重启5次 StartLimitIntervalSec=60 # 超过限制后不再重启
5.1 Restart策略对比
Restart值 行为 工控适用场景 no 不重启 不需要保活的服务 on-success 正常退出才重启 很少用 on-failure 非正常退出才重启 不希望正常退出后重启的服务 on-abnormal 被信号杀死时重启 信号异常重启 on-watchdog 看门狗超时重启 systemd自身看门狗 always 任何退出都重启 工控首选
工控程序无论怎么退出(崩溃、被杀、异常退出码)都应该重启,用always最简单可靠。
5.2 StartLimitBurst防无限循环
如果没有重启限制,程序反复崩溃→重启→崩溃→重启,日志刷屏、资源耗尽。StartLimitBurst=5配合StartLimitIntervalSec=60,意思是1分钟内重启5次就放弃——跟守护脚本的防无限重启逻辑一样,但一个配置项就搞定了。
六、多服务依赖管理
工控系统通常有多个服务,启动顺序和依赖关系必须正确。假设一个数据采集系统有三个服务:
serial-port.service:串口通信服务data-collector.service:数据采集服务(依赖串口)data-upload.service:数据上传服务(依赖采集和网络)
6.1 依赖配置
# serial-port.service [Unit] Description=串口通信服务 After=network.target #>6.2 依赖失败的处理Requires强依赖:前置服务失败 → 当前服务不启动。工控中这是正确行为——串口没起来,采集服务启动了也白搭。
Wants弱依赖:前置服务失败 → 当前服务仍启动。适用于"最好有但不是必须"的场景——比如时间同步,没同步服务也能跑,只是日志时间可能不准。
七、环境变量与工作目录配置
7.1 Environment配置
[Service] # 单行设置一个变量 Environment=CONFIG_PATH=/etc/app/config.ini Environment=LOG_LEVEL=INFO Environment=DEVICE_PORT=/dev/ttyS0 # 或用文件批量加载 EnvironmentFile=/etc/app/env.conf
env.conf文件内容:
CONFIG_PATH=/etc/app/config.ini LOG_LEVEL=INFO DEVICE_PORT=/dev/ttyS0 BAUD_RATE=115200
EnvironmentFile更适合工控场景——配置集中在一个文件,改配置只改文件,不用改Unit文件再reload。
7.2 WorkingDirectory配置
[Service] WorkingDirectory=/opt/app
设置进程的工作目录,相当于在程序启动前执行了cd /opt/app。工控程序经常需要相对路径读写文件,指定工作目录避免路径混乱。
八、systemd日志查看
systemd自带日志系统journald,所有通过systemd启动的服务日志自动收集,不需要程序自己管日志文件。
# 查看指定服务的全部日志journalctl-u># 查看最近1小时的日志journalctl-u>--since"1 hour ago"# 实时跟踪日志(类似tail -f)journalctl-u>-f# 查看服务本次启动后的日志journalctl-u>-b# 查看内核日志(排查驱动问题)journalctl-k# 只看错误级别日志journalctl-u>-perr
日志级别优先级:emerg(0)>alert(1)>crit(2)>err(3)>warning(4)>notice(5)>info(6)>debug(7)。
工控设备现场没有屏幕,远程SSH上去查日志是唯一手段。journalctl比翻/var/log目录高效得多——按服务过滤、按时间过滤、按级别过滤,一条命令定位问题。
九、完整实战:创建一个采集服务的Unit文件并部署
把前面的知识点串起来,完成一个完整的工控服务部署流程:
9.1 创建Unit文件
# /etc/systemd/system/data-collector.service [Unit] Description=工业数据采集服务 Documentation=https://wiki.company.com/data-collector After=network.target serial-port.service Requires=serial-port.service Wants=time-sync.service [Service] Type=simple ExecStart=/opt/app/data_collector --config /etc/app/config.ini Restart=always RestartSec=5s StartLimitBurst=5 StartLimitIntervalSec=60 WorkingDirectory=/opt/app EnvironmentFile=/etc/app/env.conf StandardOutput=journal StandardError=journal # 资源限制(可选) CPUAffinity=0-1 MemoryMax=256M [Install] WantedBy=multi-user.target
9.2 创建环境变量文件
# /etc/app/env.conf DEVICE_PORT=/dev/ttyS0 BAUD_RATE=115200 LOG_LEVEL=INFO DATA_DIR=/opt/app/data UPLOAD_SERVER=192.168.1.100:8080
9.3 部署与验证
# 1. 拷贝Unit文件sudocp># 2. 重新加载systemd配置(让systemd识别新Unit)sudosystemctl daemon-reload# 3. 设置开机自启sudosystemctlenable># 4. 立即启动sudosystemctl start># 5. 查看状态sudosystemctl status># 期望输出:# Active: active (running) since ...# 6. 查看日志journalctl-u>-f# 7. 测试异常重启:手动杀进程sudokillalldata_collector# 5秒后观察服务是否自动恢复sudosystemctl status># 期望:Active: active (running),进程已自动重启
9.4 更新服务配置
修改Unit文件后必须执行两步:
# 修改了Unit文件后sudosystemctl daemon-reload# 重载配置sudosystemctl restart># 重启服务使配置生效
只改了EnvironmentFile或环境变量文件,只需restart,不需要daemon-reload——因为EnvironmentFile是每次启动时动态读取的。
systemd把工控服务管理从"写Shell脚本运维"变成了"写配置文件声明"——从手工作坊到标准化工厂,这正是工控系统需要的转变。配置即运维,声明即保活,日志即诊断。