stdin 关闭时进程未关闭

stdin 关闭时进程未关闭

我正在用一个程序启动一个进程。我希望进程在程序终止时终止,因为它失去了标准输入。

我终止了程序,然后去proc/pid/fd查看该进程,发现它的stdin仍然链接到/dev/pts/2。

在这种情况下为什么进程不会关闭?更好的是,是否有一个包装器或技术可以用来确保程序在其标准输入管道关闭时关闭?

答案1

stdin是文件描述符0。关闭进程的文件描述符只能由进程本身主动完成。当进程决定关闭它时,stdin 就会关闭。

现在,当进程的 stdin 是管道的读取端时,管道的另一端可以由一个或多个其他进程打开。当另一端的所有文件描述符都已关闭时,从该管道读取将读取仍在该管道中的剩余数据,但最终不会返回任何内容(而不是等待更多数据),这意味着文件结束。

当发生这种情况时,像catcutwc... 这样从标准输入读取的应用程序通常会退出,因为它们的作用是处理输入直到最后,直到没有更多的输入。

没有什么神奇的机制会导致应用程序在输入结束时死亡,只有它们在发生这种情况时决定退出。

在:

echo foo | cat

一旦echowrite "foo\n",它就会退出,这会导致管道的写入端关闭,然后另一端read()的 did by返回 0 字节,这表明没有更多内容可读取,然后决定退出。catcatcat

echo foo | sleep 1

sleep仅在 1 秒过去后退出。它的标准输入成为一个封闭的管道与此无关,sleep甚至没有从它的标准输入中读取。

它在管道(或套接字)的写入端有所不同。

当读取端的所有 fd 都已关闭时,任何对写入端打开的 fd 进行写入的尝试都会导致向进程发送 SIGPIPE,导致进程死亡(除非它忽略该信号,在这种情况下会write()失败并显示EPIPE) 。

但这只有当他们尝试写作时才会发生。

例如,在:

sleep 1 | true

即使true立即退出并且读取端立即关闭,sleep也不会被杀死,因为它不会尝试写入其标准输出。


现在,关于/proc/fd/pid/n在输出中显示为红色ls -l --color(如你的问题的第一个版本),这只是因为对该符号链接的结果ls执行 a来尝试确定链接目标的类型。lstat()readlink()

对于在管道、套接字或其他命名空间中的文件或已删除文件上打开的文件描述符,其结果readlink将不是文件系统上的实际路径,因此第二个lstat()完成的操作ls将失败,并ls会认为这是一个损坏的符号链接,并且损坏了符号链接呈现为红色。无论管道的另一端是否关闭,您都可以通过任何管道任意一端的 fd 获得该结果。ls --color=always -l /proc/self/fd | cat例如尝试一下。

要确定 fd 是否指向损坏的管道,在 Linux 上,您可以尝试lsof使用该-E选项。

$ exec 3> >(:) 4> >(sleep 999)
$ lsof -ad3-4 -Ep "$$"
COMMAND   PID     USER   FD   TYPE DEVICE SIZE/OFF    NODE NAME
zsh     13155 stephane    3w  FIFO   0,10      0t0 5322414 pipe
zsh     13155 stephane    4w  FIFO   0,10      0t0 5323312 pipe 392,sleep,0r

对于 fd 3,lsof 无法在管道的读取端找到任何其他进程。但请注意,您可能会得到如下输出:

$ exec 5<&3
$ lsof -ad3-5 -Ep "$$"
COMMAND   PID     USER   FD   TYPE DEVICE SIZE/OFF    NODE NAME
zsh     13155 stephane    3w  FIFO   0,10      0t0 5322414 pipe 13155,zsh,5w
zsh     13155 stephane    4w  FIFO   0,10      0t0 5323312 pipe 392,sleep,0r
zsh     13155 stephane    5w  FIFO   0,10      0t0 5322414 pipe 392,sleep,3w 13155,zsh,3w

fds 3 和 5 仍然是损坏的管道,因为没有 fd 到读取端(lsof 似乎有一个错误,因为它sleep的 fd 3 也打开到损坏的管道这一事实并没有在任何地方反映出来)。


要在标准输入上打开的管道失去最后一个写入器(损坏)后立即终止进程,您可以执行以下操作:

run_under_watch() {
  perl -MIO::Poll -e '
     if ($pid = fork) {
       $SIG{CHLD} = sub {
         wait;
         exit($? & 127 ? ($? & 127) + 128 : $? >> 8);
       };
       $p = IO::Poll->new; $p->mask(STDIN, POLLERR); $p->poll;
       kill "TERM", $pid;
       sleep 1;
       kill "KILL", $pid;
       exit(1);
     } else {
       exec @ARGV
     }' "$@"
 }

它会监视 stdin 上的错误情况(在 Linux 上,只要没有编写器离开,即使管道中还有数据,这种情况似乎就会发生),并在发生时立即终止子命令。例如:

 sleep 1 | run_under_watch sleep 2

sleep 21秒后将终止该进程。

一般来说,这样做有点愚蠢。这意味着您可能会在命令有时间处理其输入末尾之前终止该命令。例如,在:

 echo test | run_under_watch cat

您会发现cat有时在它有时间输出(甚至读取!)之前就被杀死了"test\n"。没有办法解决这个问题,我们的观察者无法知道命令需要多少时间来处理输入。我们所能做的就是在kill "TERM"希望命令足以读取管道中剩余的内容并执行它需要执行的操作之前给出一个宽限期。

相关内容