答案1
引用 bash 文档(来自man bash
):
JOB CONTROL
Job control refers to the ability to selectively stop
(suspend) the execution of processes and continue (resume)
their execution at a later point. A user typically employs
this facility via an interactive interface supplied jointly
by the operating system kernel's terminal driver and bash.
因此,很简单地说,拥有(交互式 shell 的默认设置)允许人们使用诸如和 之set -m
类的内置函数,这将在(非交互式 shell 的默认设置)下被禁用。fg
bg
set +m
然而,对我来说,作业控制和退出时杀死后台进程之间的联系并不明显,但我可以确认存在一个:set -m; (sleep 10 ; touch control-on) &
如果在键入该命令后立即退出 shell,则运行将创建该文件,但set +m; (sleep 10 ; touch control-off) &
不会。
我认为答案在于以下文档的其余部分set -m
:
-m Monitor mode. [...] Background pro‐
cesses run in a separate process group and a line con‐
taining their exit status is printed upon their comple‐
tion.
这意味着启动的后台作业set +m
不是实际的“后台进程”(“后台进程是进程组 ID 与终端的进程组 ID 不同的进程”):它们与启动它们的 shell 共享相同的进程组 ID,而不是拥有自己的进程组 ID。进程组就像适当的后台进程。这解释了当 shell 在某些后台作业之前退出时观察到的行为:如果我理解正确,退出时,会向与 shell 相同进程组中的进程发送一个信号(从而杀死在 下启动的后台作业set +m
),但不会到其他进程组的进程(因此不考虑在 下启动的真正后台进程set -m
)。
因此,在您的情况下,startup.sh
脚本可能会启动后台作业。当此脚本以非交互方式运行时(例如通过 SSH,如您链接到的问题中所示),作业控制被禁用,“后台”作业共享远程 shell 的进程组,因此一旦 shell 退出就会被终止。相反,通过在该 shell 中启用作业控制,后台作业将获取自己的进程组,并且在其父 shell 退出时不会被终止。
答案2
我在 github 问题列表中找到了这个,我认为这确实回答了你的问题。
这并不是真正的 SSH 问题,更多的是围绕 BASH 非交互/交互模式和信号传播到进程组的微妙行为。
以下是基于 https://stackoverflow.com/questions/14679178/why-does-ssh-wait-for-my-subshells-without-t-and-kill-them-with-t/14866774#14866774 和http://www.itp.uzh.ch/~dpotter/howto/daemonize,有些假设尚未完全验证,但对其工作原理的测试似乎已得到证实。
pty/tty = false
启动的 bash shell 连接到已启动进程的 stdout/stderr/stdin,并保持运行,直到没有任何内容附加到套接字并且它的子进程退出。一个好的守护进程将确保它不会等待其子进程退出,分叉子进程然后退出。在此模式下,不会通过 SSH 向子进程发送 SIGHUP。我相信这对于执行处理自身去恶魔化并且不需要后台运行的进程的大多数脚本来说都可以正常工作。如果 init 脚本使用“&”来后台进程,则主要问题可能是后台进程是否尝试从 stdin 读取,因为如果会话已终止,这将触发 SIGHUP。
pty/tty = 真*
如果 init 脚本在后台启动进程,则父 BASH shell 将向 SSH 连接返回退出代码,该连接将立即退出,因为它不会等待子进程终止,也不会在 stdout 上被阻止/stderr/stdin。这将导致 SIGHUP 被发送到父 bash shell 进程组,由于作业控制在 bash 中的非交互模式下被禁用,因此该进程组将包括刚刚启动的子进程。如果守护进程在分叉时或在分叉进程中显式启动新进程会话,那么它或其子进程将不会从退出的 BASH 父进程接收到 SIGHUP。请注意,这与挂起的作业不同,挂起的作业会看到 SIGTERM。我怀疑这个问题有时与轻微的竞争条件有关。如果你看看去妖魔化的标准方法—— http://www.itp.uzh.ch/~dpotter/howto/daemonize,您将在代码中看到新会话是由分叉进程创建的,该进程在父进程退出之前可能不会运行,从而导致上述随机成功/失败行为。 sleep 语句将为分叉进程提供足够的时间来创建新会话,这就是它在某些情况下起作用的原因。
pty/tty = true 并且在 bash 中显式启用作业控制
SSH 不会连接到 bash shell 或任何启动的子进程的 stdout/stderr/stdin,这意味着一旦父 bash shell 开始执行完请求的命令,它就会退出。在这种情况下,在显式启用作业控制的情况下,由 bash shell 使用“&”启动的任何进程将立即放入单独的会话中,并且当 BASH 会话的父进程退出时,不会收到 SIGHUP 信号(在本例中为 SSH 连接)。
需要修复什么
我认为这些解决方案只需要在 run/sudo 操作文档中明确提及,作为使用后台进程/服务时的特殊情况。基本上,要么使用“pty = false”,要么在不可能的情况下,显式启用作业控制作为第一个命令,并且行为将是正确的。