为啥 ssh 在没有 -t 的情况下等待我的子 shell,然后用 -t 杀死它们?

Posted

技术标签:

【中文标题】为啥 ssh 在没有 -t 的情况下等待我的子 shell,然后用 -t 杀死它们?【英文标题】:Why does ssh wait for my subshells without -t, and kill them with -t?为什么 ssh 在没有 -t 的情况下等待我的子 shell,然后用 -t 杀死它们? 【发布时间】:2013-01-18 16:56:56 【问题描述】:

我有一个 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 的子shell,甚至没有为它们分配tty...所以我不明白它们是如何与我的ssh 连接关联的。

我后来试过这个:

ssh -t user@host "start.sh"

现在进程有一个分配的伪终端。现在,我发现 ssh 确实会在 start.sh 退出时立即退出,但它也会杀死子进程。

我猜想在后一种情况下子进程正在发送 SIGHUP,所以我这样做了:

ssh -t user@host "nohup start.sh"

这确实有效!所以,我对我的实际问题有一个解决方案,但我想在这里掌握 SIGHUP/tty 东西的微妙之处。

总之,我的问题是:

    为什么即使在start.sh 退出后,ssh(不带 -t)还要等待子进程,即使它们的父 pid 为 1? 为什么 ssh(带 -t)会杀死子进程,显然是使用 SIGHUP,即使当我从终端运行它们并从该终端注销时不会发生这种情况?

【问题讨论】:

PS:我打算阅读和消化The TTY demystified,如果我有时间的话,也许我可以回答上面的问题...... 【参考方案1】:

我想我现在可以解释了!我必须通过阅读The TTY Demystified 了解一下什么是会话和流程组。

    为什么 ssh(不带 -t)在 start.sh 退出后仍等待子进程,即使它们的父 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) 所述:

如果会话有一个控制终端,... [and] 会话领导者退出,SIGHUP 信号将被发送到控制终端的前台进程组中的每个进程。

相反,当使用交互式登录时,bash 处于交互模式,这意味着默认情况下启用作业控制,因此子进程进入单独的进程组,并且在我退出时永远不会收到 SIGHUP。

我可以通过使用set -m 在 bash 中启用作业控制来恢复我想要的行为。如果我将set -m 添加到start.sh,则ssh 退出时不再杀死孩子。

谜团解开了 :)

【讨论】:

【参考方案2】:

我怀疑(但我假设)当没有 tty 时,bash 会将 SIGHUP 传递给您的分叉进程,该进程自己处理信号,并悄悄地忽略它并继续占用 SSH 会话。

但是,由于您和进程之间有一个 tty,tty 驱动程序正在拦截 SIGHUP,意识到它已经失去了用户,并在没有 ssh 会话作为父级的情况下分叉自己运行。

【讨论】:

【参考方案3】:

在您不希望此 SIGHUP 发生的任何呼叫前添加“nohup”。

【讨论】:

以上是关于为啥 ssh 在没有 -t 的情况下等待我的子 shell,然后用 -t 杀死它们?的主要内容,如果未能解决你的问题,请参考以下文章

为啥我的Androidstudio创建project没有xml和.java文件,而且好多红点?

如何在没有服务器的情况下在本地测试我的 ssh 密钥

为啥我的SSH可以用,而sftp却不可以用

为啥 ssh 不要求时 git 要求输入密钥密码?

为啥我的子视图没有显示在堆栈顶部?

为啥在 vuejs2 中我的默认道具没有传递给我的子组件?