首页 / 内容指南 / 当前文章

systemd 的 KillMode、KillSignal、RestartKillSignal、FinalKillSignal、SendSIGKILL、TimeoutStopSec 和 systemctl kill 有什么区别?

发布于 2026-08-26 · Content Fleet 编辑部

systemd 的 KillMode、KillSignal、RestartKillSignal、FinalKillSignal、SendSIGKILL、TimeoutStopSec 和 systemctl kill 有什么区别?

核心区别表

systemd 为什么按 cgroup 管理服务进程

systemd 启动服务时,会把相关进程放入该 unit 的 Linux control group。传统 PID 文件只能指出一个进程,wrapper、worker、fork 出来的子进程可能无法完整追踪;cgroup 则让服务管理器看到整个进程集合,并把资源限制和生命周期绑定到 unit。

这也是 KillMode=control-group 通常最安全的原因:服务停止后,unit 的 cgroup 应当清空。若只杀主 PID,遗留 worker 仍可能监听端口、持有锁、写文件或消耗 CPU,但 systemctl status 却显示服务已停止。

cgroup 不是 Unix process group。终端按进程组发送 Ctrl-C,与 systemd 按 unit cgroup 选择进程不是同一个边界。服务依赖“给整个前台进程组发 SIGINT”的行为时,需要明确设计,不要假设 systemd 会模拟终端。

标准 `systemctl stop` 的停止顺序

对一个成功启动的 service,停止流程可概括为:

1. 如果定义了 ExecStop=,按配置顺序执行停止命令。

2. ExecStop= 完成后,systemd 认为服务应进入终止阶段。

3. 仍存活的进程按 KillMode= 选定。

4. systemd 发送 KillSignal=;restart 场景可改用 RestartKillSignal=

5. 若启用 SendSIGHUP=,对相同初始目标追加 SIGHUP。

6. systemd 等待进程退出,受 TimeoutStopSec= 约束。

7. 超时或满足相应结束条件后,如 SendSIGKILL=yes,向仍存活目标发送 FinalKillSignal=

8. 运行适用的 ExecStopPost= 做事后清理与诊断。

具体状态机会受 service 类型、失败模式、主进程是否已退出和 systemd 版本影响,但这条链足以解释大多数“为什么先 TERM 后 KILL”的现象。

`ExecStop=` 与 `KillSignal=` 不是二选一

ExecStop= 是服务专用的停止命令,例如调用本地管理 socket、执行数据库 shutdown 或让应用保存状态。它可以比通用 Unix 信号表达更丰富的协议。KillSignal= 则是 systemd 在终止阶段直接发送的信号。

ExecStop= 只是异步发出“请退出”后立即返回,systemd 随后会按 KillMode/KillSignal 处理剩余进程,可能让应用还没完成清理就再次收到终止信号。上游文档明确建议停止命令应同步等待服务完成退出,而不是只触发异步请求。

没有配置 ExecStop= 时,systemd 会直接发送配置的停止信号。应用能通过 SIGTERM 正确优雅退出时,未必需要额外的 shell 脚本。

`KillMode=control-group`:停止整个 unit 的进程集合

control-group 表示正常停止时,对该 unit cgroup 中剩余进程发送初始终止信号;超时后的最终信号也作用于剩余进程。它能确保服务及其 worker 一起进入停止流程,是多数服务的合理选择。

优点是不会让子进程逃离生命周期管理。风险是某些设计不良的 helper 进程本不该与主服务共同生死,却被放在同一 cgroup 中。正确修复通常是把独立生命周期组件拆成单独 unit,而不是改成 KillMode=process 留下它们。

`KillMode=mixed`:先温和处理主进程,再清理整个 cgroup

mixed 在初始阶段只把 KillSignal= 发给主进程;如果主进程退出或等待超时,最终强制信号会针对 cgroup 中所有剩余进程。其思路是让主进程负责协调 worker 优雅退出,同时保留最后清场能力。

它适合主进程确实能可靠通知并回收子进程的程序。若主进程先退出但 worker 仍在做必要清理,最终阶段可能很快清理剩余进程;应用应通过真实版本和工作负载验证时序,而不是仅凭配置名称推断宽限期。

`KillMode=process`:只处理主进程,通常不推荐

process 表示 systemd 只杀主进程,不主动终止同一 cgroup 中其他进程。上游文档明确不推荐该模式,因为子进程可以在 unit 被认为已停止后继续运行,逃离服务管理器预期的生命周期和资源管理。

如果使用它只是为了“让 worker 继续跑完”,更稳妥的设计是把 worker 建模为独立 service/scope,或让主进程在退出前同步等待它们。依赖残留进程是隐式状态,会让 restart、upgrade 和 shutdown 变得不可预测。

`KillMode=none`:systemd 不负责清理进程,强烈不建议

none 只执行停止命令,不再由 systemd 发送终止信号。若停止命令失败、遗漏某些进程或立即返回,unit cgroup 可能继续存在,进程仍运行。

这不是“最优雅”的模式,而是把所有生命周期责任交给外部逻辑。除非常特殊且经过验证的场景,否则会造成端口占用、重复实例和关机卡住。不要用它规避一个不响应 SIGTERM 的程序,应先修复程序或设计可靠的同步 ExecStop。

`KillSignal=`:正常终止阶段的第一个信号

KillSignal= 指定停止服务时发送的初始信号,默认通常为 SIGTERM。SIGTERM 可以被进程捕获,应用有机会停止接收新请求、完成在途任务、刷新缓冲、关闭数据库连接并退出。

有些前台程序把 SIGINT 当作与 Ctrl-C 相同的优雅退出请求,可配置 KillSignal=SIGINT;但必须确认该程序在无终端环境下的信号处理。不要根据名字猜测,应该用测试环境观察日志、退出码和子进程清理。

SIGKILL 无法被捕获、阻塞或忽略,不适合作为初始优雅信号。把 KillSignal=SIGKILL 会直接跳过应用清理机会,效果更接近强杀。

`RestartKillSignal=`:仅覆盖 restart 的初始终止信号

systemctl restart 在 systemd 中本质上是 stop 后再 start。默认情况下,restart 的停止阶段使用 KillSignal=;设置 RestartKillSignal= 后,可以仅在 restart 时选择不同的初始信号。

这适用于程序把“重启退出”与“永久停止”区分开的少数场景。但它不会把 restart 变成 reload,也不会绕过 ExecStop=TimeoutStopSec= 和最终清理逻辑。配置不同信号前,应确保应用对两种信号的语义明确且测试充分。

`FinalKillSignal=`:宽限期结束后的最后手段

如果正常停止后仍有进程存活,且允许最终强制终止,systemd 会发送 FinalKillSignal=,默认是 SIGKILL。它的职责是确保 unit 最终被清空,而不是提供第二次优雅退出机会。

可以把 FinalKillSignal 改为能产生 core dump 的信号,例如为诊断卡死服务选择合适的异常终止信号,但还要配置 core size、systemd-coredump 存储与敏感信息控制。更换最终信号不是延长宽限期;等待时长仍由 TimeoutStopSec 等设置决定。

`SendSIGKILL=`:是否允许最终强制清场

SendSIGKILL=yes 表示正常终止后还有进程时,systemd 可以发送 FinalKillSignal。默认开启能保证服务不会无限残留。

设置为 no 后,忽略初始信号的进程可能继续存在。这样做并不能让应用更“安全”,反而可能让 stop、restart 或关机无法收敛。只有在明确理解遗留进程、cgroup 与后续 start 行为的情况下才考虑关闭,并必须有外部恢复措施。

`SendSIGHUP=`:初始终止信号后的附加信号

启用 SendSIGHUP=yes 时,systemd 在发送 KillSignal 后向相关进程追加 SIGHUP。它常用于通知 shell 或连接相关进程控制终端/会话结束,但不是所有应用都把 SIGHUP 解释为退出;很多守护进程会把它当成重新加载配置。

因此不能把它视为“再保险的停止信号”。同时收到 SIGTERM 与 SIGHUP 的先后处理也取决于程序。只有应用协议明确需要时才启用。

`TimeoutStopSec=`:同时约束停止命令和进程退出等待

TimeoutStopSec= 有两层作用。首先,它限制每条 ExecStop= 命令的等待时间;某条超时时,后续停止命令可能被跳过,并进入终止流程。其次,它限制服务进程自行退出的等待时间;到期后,在启用最终强杀时发送 FinalKillSignal。

如果没有 ExecStop,服务会较快收到初始 KillSignal,然后 systemd 等待它退出,超时后才进入最终阶段。常见误解是“systemd 先等 TimeoutStopSec,再发 SIGTERM”,实际通常是先发初始终止信号,再等待宽限期。

设得太短会截断数据库 checkpoint、大文件刷盘或连接 draining;设为 infinity 则可能让关机永远卡在一个坏服务。应测量真实的 P99 停止时长,设置有余量但有边界的值,并让应用输出结构化停止阶段日志。

`EXTEND_TIMEOUT_USEC=`:受控延长,而非无限等待

支持 sd_notify 的服务可在停止过程中发送 EXTEND_TIMEOUT_USEC=... 延长当前超时。第一次扩展通知必须在原 TimeoutStopSec 到期前发送;超过原时间后,还要在每个新声明的间隔内持续发送,直到退出。

这适合确实需要可观测长清理的 Type=notify 服务,例如正在安全刷写状态。它不应被用来掩盖死锁。应用应同时发送 STATUS 信息,记录进度,并保留运维可强制结束的上限与手册。

`TimeoutStopFailureMode=`:超时时 terminate、abort 还是 kill

较新 systemd 还提供 TimeoutStopFailureMode=。默认 terminate 使用 KillSignal 进入常规终止,再在等待后使用 FinalKillSignal;abort 使用 WatchdogSignal 并配合 TimeoutAbortSec,适合保留故障诊断;kill 则直接发送 FinalKillSignal,不再给额外宽限期。

这项设置与本文的信号链直接相关,但不同发行版 systemd 版本可能不支持。上线前用 systemd --version 和本机 man systemd.service 核对,而不是照抄最新上游配置。

`systemctl kill`:发信号,不是停止 unit

systemctl kill myapp.service 用于向 unit 中的进程发送指定 Unix 信号。它不等价于 systemctl stop:通常不会依次运行 ExecStop、等待 TimeoutStopSec、执行完整的状态转换和清理链。

它适合发送诊断或应用自定义信号,例如让进程 dump 状态、重新打开日志或触发特定动作。使用 --signal= 选择信号,使用当前版本支持的 --kill-whom= 或相关选项选择 main、control-group 等目标范围。

命令选项在旧 systemd 版本上可能不同。脚本应先确认目标主机版本,不能以开发机最新 man page 为唯一依据。发送 SIGKILL 前还要确认作用范围,避免把整个 cgroup 中的辅助进程一起不可恢复地终止。

`systemctl stop`、`restart`、`kill` 与 `reload` 的区别

  • stop:进入 unit 停止事务,执行停止生命周期。
  • restart:完成 stop,再执行 start;会经历停止信号链。
  • kill:主动发信号,通常不代表完整停止事务。
  • reload:请求运行中的应用重读配置,成功后通常仍是同一进程实例。

给主进程发 SIGHUP 不自动等于 systemd 已确认 reload 完成。需要有序依赖时,应配置能同步确认完成的 ExecReload,或使用支持通知式 reload 的服务类型。

一个安全的停止配置示例

[Service]
Type=notify
ExecStart=/usr/local/bin/myapp
KillMode=control-group
KillSignal=SIGTERM
TimeoutStopSec=45s
SendSIGKILL=yes
FinalKillSignal=SIGKILL

应用收到 SIGTERM 后停止接单,等待在途任务,周期性报告停止进度并退出。45 秒后仍未退出时,systemd 清理剩余进程。这个模板不是通用答案:数据库、大型 JVM、队列消费者和短任务需要不同的宽限期与信号协议。

如何验证服务真正优雅停止

1. 使用 systemctl show 核对实际生效的 KillMode、信号和 Timeout。

2. 用 systemctl cat 确认 vendor unit 与 drop-in 的合并结果。

3. 启动带 worker 的真实测试负载,而不是只启动空进程。

4. 执行 stop,记录主进程与子进程收到的信号和时间。

5. 检查在途请求、队列 offset、数据库事务和文件是否完整。

6. 确认 unit cgroup 最终为空,端口和锁已释放。

7. 故意让应用忽略 SIGTERM,验证 Timeout 与 FinalKillSignal。

8. 执行 restart,确认旧实例完全结束后新实例再占用资源。

9. 检查 Journal 中的 result、exit code、signal 和耗时。

10. 在目标发行版和 systemd 版本上重复测试。

常见错误

错误一:用 `KillMode=process` 解决子进程被杀

这只是让子进程逃离 unit 生命周期。应拆分 unit,或让主进程可靠协调 worker 退出。

错误二:ExecStop 只发送一个异步命令后立即返回

systemd 随即进入剩余进程终止阶段,可能打断清理。ExecStop 应同步等待真正停止,或让应用直接处理 KillSignal。

错误三:把 TimeoutStopSec 设为 infinity

一次死锁即可阻塞 restart 或系统关机。优先设置有数据支持的上限,并通过 sd_notify 受控扩展。

错误四:认为 SIGTERM 一定会优雅退出

SIGTERM 只是可处理信号。应用可能忽略、阻塞或处理逻辑死锁,必须测试并保留最终清场机制。

错误五:用 `systemctl kill` 代替 stop

kill 不保证执行完整 unit 停止事务。常规下线用 stop;kill 留给诊断和明确的信号操作。

错误六:只观察主 PID 消失

worker 可能仍在 cgroup 中。应检查 systemctl statussystemd-cgls、cgroup.procs、监听端口和业务状态。

FAQ

1. `systemctl stop` 默认先发送 SIGTERM 还是先等待 TimeoutStopSec?

若没有需要执行的 ExecStop,通常先按 KillMode 发送 KillSignal,默认 SIGTERM,再等待进程退出;超时后才发送 FinalKillSignal。若有 ExecStop,它先参与停止流程并也受超时约束。

2. `KillMode=mixed` 与 `control-group` 最大区别是什么?

control-group 在初始终止阶段面向整个 unit cgroup;mixed 先把温和信号发给主进程,最终阶段再清理整个 cgroup 的剩余进程。

3. 设置 `KillSignal=SIGINT` 后还会发送 SIGKILL 吗?

如果进程在 TimeoutStopSec 内未退出,且 SendSIGKILL 启用,仍会发送 FinalKillSignal,默认 SIGKILL。KillSignal 只决定初始信号。

4. `SendSIGKILL=no` 是否能保证数据不丢?

不能。它只禁止最终强杀,可能留下死锁进程、锁和端口。数据安全应通过应用协议、事务、同步停止和备份实现。

5. `systemctl restart` 会跳过 ExecStop 吗?

不会把 restart 当作简单启动覆盖。restart 通常是 stop 后 start,成功启动过的服务会执行适用的 ExecStop 和 ExecStopPost,再进入新启动。

6. 为什么服务已显示 stopped,子进程还在?

常见原因是 KillMode=process/none、子进程移出 unit cgroup、错误的 forking/PID 识别或外部程序另行启动进程。先检查实际 cgroup 和 unit 合并配置。

7. FinalKillSignal 可以换成 SIGABRT 吗?

可以在支持版本中配置其他信号,用于触发 core dump 等诊断,但要同时考虑应用信号处理、core 限制、存储空间和敏感数据。它仍是最终阶段信号。

8. 怎么知道本机支持哪些配置项?

查看 systemd --version、本机 man systemd.killman systemd.servicesystemd-analyze verify。最新上游文档不保证旧发行版已包含相同选项。

参考来源

  • systemd 上游:systemd.kill 配置文档源文件 — https://github.com/systemd/systemd/blob/main/man/systemd.kill.xml
  • systemd 上游:systemd.service 配置文档源文件 — https://github.com/systemd/systemd/blob/main/man/systemd.service.xml
  • systemd 上游:systemctl 命令文档源文件 — https://github.com/systemd/systemd/blob/main/man/systemctl.xml
  • systemd 上游:sd_notify 文档源文件 — https://github.com/systemd/systemd/blob/main/man/sd_notify.xml
  • Linux man-pages:signal(7) — https://man7.org/linux/man-pages/man7/signal.7.html
  • Linux Kernel 官方文档:Control Group v2 — https://docs.kernel.org/admin-guide/cgroup-v2.html