为何Docker下/bin/sh会向java进程传播SIGTERM却不对sleep进程生效?
现象解释
前置核心规则
- Linux 内核会对 PID 1 进程做特殊信号处理:所有未被进程显式注册处理函数的信号,都会被直接丢弃,不会执行普通进程对应的默认动作(比如
SIGTERM的默认动作是终止进程,对未注册对应 handler 的 PID 1 进程完全无效)。 - POSIX 标准的
sh实现(如 dash、busybox sh 等,非 bash)执行sh -c "命令串"时,存在 exec 优化逻辑:如果命令串是单个无后续执行逻辑的简单命令,sh会直接调用exec()系统调用,将自身进程镜像完全替换为目标命令的进程,目标命令继承sh原有的 PID,原sh进程不复存在。
两种场景的差异原因
场景1:/bin/sh -c java ... 信号传递正常
你的 Java 启动命令符合 sh -c 的 exec 优化条件,sh 执行时直接把自身替换为 Java 进程,此时 Java 就是 PID 1 进程。
Java 虚拟机默认会主动注册 SIGTERM 信号的处理函数,收到信号后会触发所有注册的 shutdown hooks 执行,完成清理逻辑后再正常退出,因此你会观察到信号传递正常、钩子正常执行的现象。
场景2:/bin/sh -c sleep ... 无法被 SIGTERM 终止
该场景存在两种可能的原因:
- 符合 exec 优化条件:
sh直接替换为 sleep 进程,sleep 作为 PID 1 运行。sleep 程序本身没有注册SIGTERM信号的处理函数,按照 PID 1 的信号规则,收到的SIGTERM会被内核直接丢弃,不会触发终止进程的默认动作,sleep 会一直运行直到计时结束。 - 不符合 exec 优化条件:如果你的命令串带了后续执行逻辑(比如
/bin/sh -c "sleep 3600; echo finish"),sh无法直接替换为 sleep 进程,会 fork 出子进程运行 sleep,自身作为 PID 1 等待子进程退出。而sh本身没有注册SIGTERM的处理函数,收到信号后被内核直接丢弃,既不会自身退出,也不会主动给子进程 sleep 转发信号,因此 sleep 会一直运行。
内容的提问来源于stack exchange,提问作者Flame239
相关产品推荐
相关产品推荐

