systemd 的 ExecStartPre、ExecStart、ExecStartPost、ExecReload、ExecStop 和 ExecStopPost 有什么区别?
systemd 的 ExecStartPre、ExecStart、ExecStartPost、ExecReload、ExecStop 和 ExecStopPost 有什么区别?
六类命令快速对比
这些命令组成状态机,而不是任意脚本清单。依赖单元何时继续、超时从何时计算,都与命令所在阶段有关。
ExecStartPre 是什么
ExecStartPre= 在 ExecStart= 之前运行,多条命令按文件中的顺序串行执行。适合快速、可重复的前置检查,例如验证配置语法、检查目录权限或生成本次启动所需的小型文件。
只要某条未带忽略失败前缀的命令非零退出或异常终止,后续启动命令通常不会继续,Unit 会按失败路径进入停止处理。
ExecStartPre 不适合启动长期进程
官方文档明确不建议在 ExecStartPre= 中启动长时间存活的进程。进入下一阶段前,由前置命令派生的进程可能被终止,不能把它当作第二个后台守护进程入口。
若一个服务确实包含独立的长期组件,应拆成另一个 Unit,并用 Requires=、Wants= 和 After= 表达关系,而不是用 shell 的 & 躲避 systemd 跟踪。
ExecStart 是什么
ExecStart= 是服务主体的启动命令。对于长期守护进程,它应最终对应 systemd 能跟踪的 Main PID 或服务控制组;对于 Type=oneshot,它可以是运行后退出的任务。
大多数非 oneshot 类型通常只允许一个 ExecStart=。Type=oneshot 可以按顺序配置多条,而且只有前一条成功完成后才运行下一条。
ExecStart 与 Type 的关系
ExecStart 何时被认为“启动完成”取决于 Type=。Type=simple 通常在进程创建后较早视为已启动;Type=exec 等到执行文件成功执行;Type=notify 等待服务发送就绪通知;Type=oneshot 等待命令退出。
所以同一条 ExecStart 放在不同 Type 下,会改变依赖单元的启动时机。不能仅凭进程已经存在就断言应用已可提供服务。
ExecStartPost 是什么
ExecStartPost= 在 ExecStart= 按相应 Type 的规则完成启动之后执行。它适合服务已经就绪后才能做的动作,例如向本地代理注册、执行轻量健康确认或调整配套状态。
多条 ExecStartPost 同样按顺序执行。它们仍属于整个启动流程的一部分,依赖本 Unit 排序在后的其他 Unit 通常要等这些命令完成。
ExecStartPost 不是异步后台任务入口
若后处理需要长期运行,应该创建独立 Service 或 Timer。让 ExecStartPost 启动后台 shell,会制造 systemd 难以表达的生命周期,还可能被 KillMode 和控制组清理影响。
耗时后处理也会延长 Unit 的启动完成时间。需要并行且可独立失败的工作,应明确拆分,而不是用 & 让主 Unit 假装启动完成。
前置和后置命令失败会怎样
默认情况下,ExecStartPre、ExecStart 或 ExecStartPost 中任意一个失败,且没有通过允许的方式标记为可忽略,会使启动判定失败。随后不会继续普通启动链,并进入停止相关处理。
命令前缀 - 可以让 systemd 记录非零结果但不把它作为该命令的致命失败。它只适用于结果确实可忽略的操作,不能用来隐藏配置校验或迁移失败。
ExecReload 是什么
ExecReload= 定义 systemctl reload name.service 时执行的命令。典型做法是调用程序自带的 reload 子命令,或向正确的 Main PID 发送约定信号,让服务重新读取配置。
它不会自动因为 Unit 文件被编辑而运行。修改 Unit 文件后需要 daemon-reload 让 manager 重读定义;这与让业务进程重载自身配置是两件事。
reload 与 restart 的区别
Reload 目标是让同一服务进程或服务实例重新读取配置,通常保持连接和 PID;Restart 则执行停止再启动,进程身份和运行状态可能改变。
服务不支持可靠 reload 时,不要伪造一个始终成功的 ExecReload=/bin/true。应明确告诉运维使用 restart,否则部署系统会误以为配置已经生效。
为什么只发送信号可能不够可靠
异步信号发送命令本身很快成功,却不代表服务已完成重载。若随后依赖新的配置立即执行其他操作,可能发生竞态。
更可靠的方案是服务提供同步重载接口,或使用支持通知式 reload 的机制,使 systemd 能观察重载开始和完成。至少应在日志与状态验证中确认新配置实际生效。
ExecStop 是什么
ExecStop= 在服务此前成功启动、现在进入停止流程时运行。它应请求服务优雅终止,并等待退出完成,而不是只发送一个异步信号后立即返回。
如果没有配置 ExecStop,systemd 仍会按照 KillMode= 和停止信号处理控制组进程。因此 ExecStop 是自定义优雅关闭路径,不是 systemd 能否终止服务的唯一保障。
ExecStop 何时不会运行
如果服务的启动从未成功完成,例如 ExecStartPre 或 ExecStart 失败,ExecStop= 通常不会运行。因为它面向“已经启动成功的服务”的正常停止动作。
这正是一次性清理不能只放在 ExecStop 的原因。准备阶段创建了临时资源但启动失败时,需要 ExecStopPost 覆盖失败路径。
ExecStop 必须等待关闭完成
错误示例是 ExecStop=/bin/kill -TERM $MAINPID:发送信号后命令立即退出,但主进程可能仍在保存数据。更好的停止命令应同步等待服务退出,或让 systemd 直接按标准信号和超时追踪。
停止命令结束后,如果控制组里仍有进程,systemd 会按 KillMode 继续处理。服务若需要较长时间刷盘,应合理设置 TimeoutStopSec=,而不是无限等待。
ExecStopPost 是什么
ExecStopPost= 在停止流程末尾执行,既覆盖正常停止,也覆盖启动阶段失败后的清理。适合删除本 Unit 创建的临时文件、卸载临时资源、撤销注册或把最终状态写入诊断日志。
它在服务进程按停止策略处理后运行,不应依赖主进程仍然存活。需要与应用协商的优雅动作应放在 ExecStop,不是 ExecStopPost。
ExecStopPost 能读取哪些状态
systemd 为停止后命令提供服务结果和退出信息相关的环境变量,例如 $SERVICE_RESULT、$EXIT_CODE 与 $EXIT_STATUS。清理脚本可以据此区分成功、超时、信号或退出码失败,并生成安全诊断。
诊断逻辑应避免覆盖原始失败原因。清理脚本自己失败时,日志中要同时保留主体结果和清理结果,便于判断根因。
StopPost 与 finally 的相似点和差异
ExecStopPost 很像程序语言中的 finally:无论启动中途失败还是正常停止,都尽量执行收尾。但它仍受机器断电、manager 被强制终止和文件系统故障影响,不能保证任何场景必达。
关键数据应由应用使用事务、原子写入和崩溃恢复保护,不能把 ExecStopPost 当成唯一一致性机制。
多条 Exec 命令的顺序
同一类允许配置多条命令时,systemd 按出现顺序执行;前一条未完成前,下一条不会开始。跨阶段顺序则是 StartPre、Start、StartPost,停止阶段为 Stop、进程终止处理、StopPost。
Drop-in 覆盖现有列表时要小心。对可重复指令,常需先写一个空赋值清空原列表,再写新值,否则可能是追加而不是替换。
systemd 不默认使用 Shell
ExecStart=/usr/bin/myapp --flag 会直接执行程序,不自动通过 /bin/sh -c。管道、重定向、&&、通配符和命令替换不会按交互式 Shell 的规则解释。
确实需要复杂 Shell 语法时,显式使用 /bin/sh -c '...',但更推荐把复杂流程放进有版本管理和测试的脚本。避免把密钥直接写在命令行,因为进程列表和诊断输出可能暴露参数。
命令前缀有什么作用
systemd 的执行行支持若干前缀,用于忽略失败、改变参数零、关闭特定权限调整或控制变量展开。最常见的 - 表示即使该命令异常退出,也不据此把整个阶段判为失败。
前缀是精细行为控制,不是“让服务先跑起来再说”的开关。使用前要在 systemd.service 与 systemd.exec 对应版本文档中确认语义。
TimeoutStartSec 覆盖哪些阶段
启动超时不只约束主程序,它还会影响 StartPre、Start 和 StartPost 组成的启动流程。某个校验脚本卡住,同样可能导致整个 Unit 启动超时。
长时间数据库迁移不宜随意塞进 StartPre。应设计独立、有状态、可恢复的迁移 Unit,并明确服务对它的依赖关系。
TimeoutStopSec 覆盖哪些阶段
停止超时用于限制 ExecStop 以及服务进程结束等待,并对 ExecStopPost 的运行也有相应约束。超时后 systemd 会按配置升级终止动作。
设置过短可能在数据刷盘中强杀,设置无限则会让关机或发布永久卡住。根据真实关闭耗时设定上界,并对超时告警。
KillMode 决定终止范围
KillMode=control-group 通常会处理 Unit 控制组中的所有剩余进程;mixed、process 等模式改变信号发送对象。随意使用 KillMode=none 或仅杀 Main PID,容易留下孤儿进程。
如果服务通过规范 Type 和前台模式运行,通常无需规避 systemd 进程跟踪。多守护进程应用最好拆分为多个 Unit。
推荐配置示例
[Service]
Type=notify
ExecStartPre=/usr/local/libexec/myapp-check-config
ExecStart=/usr/local/bin/myapp --foreground
ExecStartPost=/usr/local/libexec/myapp-register
ExecReload=/usr/local/bin/myappctl reload --wait
ExecStop=/usr/local/bin/myappctl stop --wait
ExecStopPost=/usr/local/libexec/myapp-cleanup
TimeoutStartSec=60s
TimeoutStopSec=90s
示例强调同步与可追踪性,具体命令必须由应用真实支持。若没有可靠的 register、reload 或 stop 接口,应删除相应行,而不是用空成功命令伪装。
修改 Unit 后如何验证
先用 systemd-analyze verify 检查 Unit 语法和依赖,再执行 systemctl daemon-reload。在测试环境启动、reload、stop,并用 systemctl status、systemctl show 与 journalctl -u 检查各阶段结果。
还要主动制造 StartPre 失败、主程序失败、StartPost 失败、reload 失败和停止超时,确认 StopPost 清理与最终状态符合预期。
常见错误配置
长期进程放 StartPre、启动脚本 fork 后让 systemd 跟丢 Main PID、用 StartPost 无限轮询、ExecReload 只发信号不验证、ExecStop 只发送信号不等待,以及把唯一清理逻辑放 ExecStop,都是常见故障源。
另一个错误是给所有命令加 -。这会把真实故障转换成表面成功,使依赖服务在前置条件未满足时继续启动。
结论
StartPre 做短期准备,Start 承载主体,StartPost 做就绪后动作;Reload 面向运行中重载,Stop 面向成功启动后的优雅关闭,StopPost 覆盖停止末尾和启动失败清理。
可靠 Unit 的关键不是命令数量,而是每条命令与状态机阶段一致、同步完成、超时有界,并由 systemd 持续跟踪真实进程。
常见问题
ExecStartPre 可以启动一个后台辅助进程吗?
不应这样做。StartPre 只适合短期准备,长期组件应拆成独立 Unit,并用依赖和排序关系连接。
ExecStartPost 执行时服务一定能处理请求吗?
取决于 Type= 是否准确表达就绪条件。Type=notify 配合正确 READY 通知通常比仅看到进程启动更可靠。
修改 service 文件后执行 reload 就够了吗?
不够。先用 systemctl daemon-reload 让 systemd 重读 Unit;业务配置是否再执行 systemctl reload,取决于服务是否实现 ExecReload。
启动失败时哪条清理命令最可靠?
ExecStopPost 专门覆盖正常停止和启动失败后的收尾。ExecStop 通常只在服务成功启动后运行。
没有 ExecStop,systemd 会不会留下进程?
通常不会。systemd 会按 KillMode、KillSignal 和 TimeoutStopSec 处理控制组。ExecStop 用于应用自定义的同步优雅关闭。
官方资料
1. systemd.service — https://www.freedesktop.org/software/systemd/man/latest/systemd.service.html
2. systemd.exec — https://www.freedesktop.org/software/systemd/man/latest/systemd.exec.html
3. systemd.kill — https://www.freedesktop.org/software/systemd/man/latest/systemd.kill.html
4. systemd.unit — https://www.freedesktop.org/software/systemd/man/latest/systemd.unit.html
5. systemctl — https://www.freedesktop.org/software/systemd/man/latest/systemctl.html
6. systemd-analyze — https://www.freedesktop.org/software/systemd/man/latest/systemd-analyze.html
7. journalctl — https://www.freedesktop.org/software/systemd/man/latest/journalctl.html