我有一个bash脚本start.sh,如下所示:
for thing in foo bar; do
{
background_processor $thing
cleanup_on_exit $thing
} &
done
这就是我想要的:我运行start.sh,它以代码0退出,并且两个子shell在后台运行。每个子shell运行background_processor
,当退出时,它运行cleanup_on_exit
。即使我退出我最初运行start.sh的终端(即使这是一个ssh连接),这也有效。
然后我尝试了这个:
ssh user@host "start.sh"
除了在start.sh
退出之后,ssh显然也等待子shell退出时,这是有效的。我真的不明白为什么。一旦start.sh
退出,子shell就会变成pid 1的子节点,甚至不会分配tty ......所以我无法理解它们是如何与我的ssh连接相关联的。
我后来试过这个:
ssh -t user@host "start.sh"
现在进程有一个指定的伪tty。现在,我发现只要start.sh
退出,ssh就会退出,但它也会杀死子进程。
我猜测在后一种情况下子进程被发送了SIGHUP,所以我这样做了:
ssh -t user@host "nohup start.sh"
实际上有效!所以,我有一个解决我实际问题的方法,但我想在这里掌握SIGHUP / tty内容的微妙之处。
总之,我的问题是:
start.sh
退出后也会等待子进程,即使它们有父pid 1?答案 0 :(得分:19)
我想我现在可以解释一下!我必须通过阅读The TTY Demystified来了解会话和流程组的内容。
- 为什么即使在start.sh退出之后ssh(没有-t)等待子进程,即使它们有父pid 1?
醇>
因为没有tty,ssh通过管道连接到shell进程的stdin / stdout / stderr(然后由子进程继承),我正在使用的OpenSSH版本(OpenSSH_4.3p2)等待这些套接字退出之前关闭。一些早期版本的OpenSSH没有这样做。对此有一个很好的解释,理由是here。
相反,当使用交互式登录(或ssh -t
)时,ssh和进程正在使用TTY,因此没有管道可以等待。
我可以通过重定向流来恢复我想要的行为。此变体会立即返回:ssh user@host "start.sh < /dev/null > /dev/null 2>&1"
- 为什么ssh(带-t)会使用SIGHUP杀死子进程,即使从终端运行并退出该终端时也不会发生这种情况?
醇>
因为bash以非交互模式启动,这意味着默认情况下禁用作业控制,因此子进程与父bash进程(会话负责人)位于同一进程组中。当父bash进程退出时,内核将SIGHUP发送到其进程组(位于前台),如setpgid(2)
中所述:
如果会话有控制终端,...... [和]会话负责人退出,则SIGHUP信号将被发送到控制终端的前台进程组中的每个进程。
相反,当使用交互式登录时,bash处于交互模式,这意味着默认情况下启用了作业控制,因此子进程进入单独的进程组,并在退出时从不接收SIGHUP。
我可以使用set -m
在bash中启用作业控制来恢复我想要的行为。如果我将set -m
添加到start.sh
,则在ssh退出时不会再杀死这些孩子。
解决了奥秘:)
答案 1 :(得分:0)
我怀疑(但我假设)当没有tty时,bash将SIGHUP传递给你的分叉进程,它正在处理信号本身,并且静静地忽略它并继续占用SSH会话。 / p>
但是,在你和进程之间有一个tty时,tty驱动程序正在拦截SIGHUP,意识到它已经丢失了用户,并且在没有ssh会话作为父进程的情况下自行运行。