为什么挂起 SSH 命令等待来自服务器上 'sshd' 中两端打开的管道的输出?

Why are hanging SSH commands waiting for output from a pipe with both ends open in 'sshd' on the server?

这是 Whosebug 上的,而不是 SuperUser/ServerFault,因为它与系统调用和 sshd 执行的 OS 交互有关,而不是我遇到的问题 using SSH(尽管也很感激 :p)。

上下文:

我通过 SSH 调用一系列复杂的脚本,例如ssh user@host -- /my/command。远程命令进行了大量复杂的分叉和执行,最终导致远程主机上的后台守护进程 运行。偶尔(我正在慢慢发疯试图找出可靠的复制条件),ssh 命令永远不会 return 控制客户端 shell。在那些情况下,我可以进入目标主机并看到一个没有 children 的 sshd: user@notty 进程无限期挂起。

解决这个问题不是这个问题的目的。这个问题是关于 sshd 进程 在做什么

SSH实现为OpenSSH,version版本为5.3p1-112.el6_7.

问题:

如果我发现其中一个卡住 sshds 和 strace 它,我可以看到它在两个手柄上做 select,例如select(12, [3 6], [], NULL, NULL 或类似的。 lsof 告诉我这些句柄之一是连接回 SSH 客户端的 TCP 套接字。另一个是管道,管道的另一端只在同一个sshd进程中打开。如果我使用 this SuperUser question 的答案按 ID 搜索该管道,唯一包含对该管道的引用的进程是同一进程。 lsof 证实了这一点:管道的读写端都在同一个进程中打开,例如(对于管道 788422703 和 sshd PID 22744):

sshd    22744 user    6r  FIFO                0,8      0t0 788422703 pipe
sshd    22744 user    7w  FIFO                0,8      0t0 788422703 pipe 

问题:

SSH 在等什么?如果管道没有连接到任何东西并且没有 child 进程,我无法想象它会发生什么事件。

"looped"pipe/what代表什么?我唯一的理论是,如果没有向 SSH 客户端提供 STDIN,目标主机 sshd 会打开一个虚拟 STDIN 管道,因此它的一些内部 child-management 代码可以更统一?但这似乎很脆弱。

SSH 是如何进入这种情况的?

我有什么Tried/Additional信息:

您似乎在描述通知管道。 OpenSSH sshd 主循环调用 select() 等待它有事可做。被轮询的文件描述符包括与客户端的 TCP 连接和用于服务活动通道的任何描述符。

sshd 希望能够在收到 SIGCHLD 信号时中断 select() 调用。为此,sshd 为 SIGCHLD 安装一个信号处理程序并创建一个管道。当接收到 SIGCHLD 信号时,信号处理程序将一个字节写入管道。管道的读取端包含在 select() 轮询的文件描述符列表中。写入管道的行为将导致 select() 调用 return 并指示通知管道可读。

所有代码都在serverloop.c:

/*
 * we write to this pipe if a SIGCHLD is caught in order to avoid
 * the race between select() and child_terminated
 */
static int notify_pipe[2];
static void
notify_setup(void)
{
        if (pipe(notify_pipe) < 0) {
                error("pipe(notify_pipe) failed %s", strerror(errno));
        } else if ((fcntl(notify_pipe[0], F_SETFD, 1) == -1) ||
            (fcntl(notify_pipe[1], F_SETFD, 1) == -1)) {
                error("fcntl(notify_pipe, F_SETFD) failed %s", strerror(errno));
                close(notify_pipe[0]);
                close(notify_pipe[1]);
        } else {
                set_nonblock(notify_pipe[0]);
                set_nonblock(notify_pipe[1]);
                return;
        }
        notify_pipe[0] = -1;    /* read end */
        notify_pipe[1] = -1;    /* write end */
}
static void
notify_parent(void)
{
        if (notify_pipe[1] != -1)
                write(notify_pipe[1], "", 1);
}
[...]

/*ARGSUSED*/
static void
sigchld_handler(int sig)
{
        int save_errno = errno;
        child_terminated = 1;
#ifndef _UNICOS
        mysignal(SIGCHLD, sigchld_handler);
#endif
        notify_parent();
        errno = save_errno;
}

设置和执行 select 调用的代码在另一个名为 wait_until_can_do_something() 的函数中。它相当长,所以我不会在这里包含它。 OpenSSH 是开源的,this page 描述了如何下载源代码。