[英]Why are hanging SSH commands waiting for output from a pipe with both ends open in 'sshd' on the server?
这是在StackOverflow而不是SuperUser / ServerFault,因为它与sshd执行的系统调用和操作系统交互有关,而不是我使用 SSH的问题(虽然也赞赏它的帮助:p)。
语境:
我通过SSH调用一系列复杂的脚本,例如ssh user@host -- /my/command
。 远程命令执行大量复杂的分支和执行,最终导致在远程主机上运行的后台守护程序进程。 偶尔(我慢慢地试图找出可靠的再现条件), ssh
命令永远不会将控制权返回给客户端shell。 在这些情况下,我可以进入目标主机并看到sshd: user@notty
进程,没有子项无限期挂起。
解决这个问题不是这个问题的关键。 这个问题是关于sshd
进程正在做什么的 。
SSH实现是OpenSSH,版本版本是5.3p1-112.el6_7。
问题:
如果我发现那些被卡住一个sshd
S和strace
它,我可以看到它在做一个选择上的两个手柄,如select(12, [3 6], [], NULL, NULL
或相似。 lsof
告诉我其中的一个handle是连接回SSH客户端的TCP套接字。另一个是管道, 另一端只在同一个sshd
进程中打开 。如果我使用这个SuperUser问题的答案通过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等待? 如果管道没有连接任何东西并且没有子进程,我无法想象它可能会发生什么事件。
什么是“循环”管道/它代表什么? 我唯一的理论是,如果STDIN没有提供给SSH客户端,目标主机sshd
打开一个虚拟STDIN管道,因此它的一些内部子管理代码可以更加统一吗? 但这看起来很脆弱。
SSH是如何进入这种情况的?
我尝试过的/附加信息:
sshd
进程,例如ssh user@host -- 'sleep 60 &'
; sshd
将等待流被关闭到守护进程; 不只是直接孩子的退出。 由于有问题的脚本最终会在启动的守护进程中导致(在进程树中向下),因此守护进程最初可能会保留在句柄上。 然而,这似乎并不成立 - 使用sleep 60 &
command作为示例,与守护进程通信的sshd
进程保持并选择四个打开的管道,而不仅仅是两个,并且至少有两个管道从sshd
连接到守护进程,而不是循环。 除非有一种跟踪/指向我不知道的管道的方法(并且可能存在 - 例如,我不知道dup
文件句柄如何发挥close()
信号量等待或管道),我不知道认为管道到自身的情况代表了等待守护进程的情况。 sshd
定期接收TCP套接字/ ssh连接本身的通信,这会在短暂的通信期间将其从select
唤醒(在此期间strace
显示它阻止SIGCHLD),然后它返回等待相同的FD。 您似乎在描述通知管道。 OpenSSH sshd主循环调用select()
等待它有事可做。 被轮询的文件描述符包括到客户端的TCP连接以及用于服务活动通道的任何描述符。
sshd希望能够在收到SIGCHLD信号时中断select()调用。 为此,sshd为SIGCHLD安装信号处理程序,并创建一个管道。 当收到SIGCHLD信号时,信号处理程序将一个字节写入管道。 管道的读取结束包含在select()轮询的文件描述符列表中。 写入管道的行为将导致select()调用返回,并指示通知管道是可读的。
所有代码都在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是开源的, 本页面描述了如何下载源代码。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.