是否在pclose()之前从popen()ed FILE *读取输出?

问题描述 投票:2回答:4

pclose()的手册页说:

pclose()函数等待关联的进程终止并返回wait4(2)返回的命令的退出状态。

我觉得这意味着如果由FILE*创建的相关popen()"r"类型打开以便读取command的输出,那么在调用pclose()之后你还不确定输出是否完成。但是在pclose()之后,封闭的FILE*肯定是无效的,所以你怎么能确定你已经阅读了command的全部输出?

要通过示例说明我的问题,请考虑以下代码:

// main.cpp

#include <iostream>
#include <cstdio>
#include <cerrno>
#include <cstring>
#include <sys/types.h>
#include <sys/wait.h>

int main( int argc, char* argv[] )
{
  FILE* fp = popen( "someExecutableThatTakesALongTime", "r" );
  if ( ! fp )
  {
    std::cout << "popen failed: " << errno << " " << strerror( errno )
              << std::endl;
    return 1;
  }

  char buf[512] = { 0 };
  fread( buf, sizeof buf, 1, fp );
  std::cout << buf << std::endl;

  // If we're only certain the output-producing process has terminated after the
  // following pclose(), how do we know the content retrieved above with fread()
  // is complete?
  int r = pclose( fp );

  // But if we wait until after the above pclose(), fp is invalid, so
  // there's nowhere from which we could retrieve the command's output anymore,
  // right?

  std::cout << "exit status: " << WEXITSTATUS( r ) << std::endl;

  return 0;
}

我的问题,如上所述:如果我们只确定产生输出的子进程已经在pclose()之后终止,我们怎么知道用fread()检索的内容是完整的?但是如果我们等到pclose()之后,fp无效,那么我们就无处可以检索命令的输出了,对吧?

这感觉就像一个鸡蛋问题,但我看到的代码与上面的代码相似,所以我可能误解了一些东西。我很感激对此的解释。

c++ posix popen pclose
4个回答
3
投票

TL; DR执行摘要:我们如何知道用fread()检索的内容是否完整? - 我们有一个EOF。

当子进程关闭管道末端时,您将获得EOF。当它明确地调用close或退出时会发生这种情况。在此之后,没有任何东西可以从管道的末端出来。获得EOF后,您不知道该进程是否已终止,但您确实知道它永远不会向管道写入任何内容。

通过调用pclose,您关闭管道的末端并等待终止孩子。当pclose回来时,你知道孩子已经终止了。

如果你在没有获得EOF的情况下调用pclose,并且孩子试图将东西写入管道的末端,那么它将失败(事实上它将获得一个SIGPIPE并且可能会死亡)。

这里鸡蛋和鸡蛋的情况绝对没有空间。


0
投票

更仔细地阅读the documentation for popen

pclose()函数应关闭由popen()打开的流,等待命令终止,并返回运行命令语言解释器的进程的终止状态。

它阻止和等待。


0
投票

我在进一步研究这个问题时学到了一些东西,我想回答我的问题:

基本上:是的,freadFILE*之前从popen返回的pclose是安全的。假设给fread的缓冲区足够大,你不会“错过”给commandpopen生成的输出。

回过头来仔细考虑fread的作用:它有效地阻塞,直到(size * nmemb)字节被读取或遇到文件结束(或错误)。

感谢C - pipe without using popen,我更了解popen在引擎盖下做了什么:它有一个dup2将其stdout重定向到它使用的管道的写端。重要的是:它执行某种形式的exec来在分叉进程中执行指定的command,并且在此子进程终止后,其打开的文件描述符(包括1stdout))将被关闭。即终止指定的command是儿童进程'stdout关闭的条件。

接下来,我回过头来仔细考虑EOF在这种背景下的真正含义。起初,我处于松散的错误和错误的印象,“fread试图尽可能快地从FILE*读取并在读取最后一个字节后返回/解除阻塞”。这不是真的:如上所述:fread将读取/阻塞,直到读取其目标字节数或遇到EOF或错误。由FILE*返回的popen来自fdopen使用的管道读取端的popen,所以当孩子处理'EOF - 与管道的写端结合的stdout - 被关闭时,它发生dup2

所以,最后我们得到的是:popen创建一个管道,其写入端获取运行指定command的子进程的输出,如果fdopened传递给FILE*,则其读取结束。 (假设fread的缓冲区足够大),fread将阻塞直到fread发生,这对应于EOF管道写入端的关闭,这是由执行的popen终止造成的。即因为command阻塞直到遇到fread,并且EOF发生在EOF之后 - 在command的子进程中运行 - 终止,使用fread(具有足够大的缓冲区)来捕获给予popencommand的完整输出是安全的。

如果有人能够验证我的推论和结论,我会感激不尽。


0
投票

popen()只是fork,dup2,execv,fdopen等系列的快捷方式。它将让我们通过文件流操作轻松访问子STDOUT,STDIN。

在popen()之后,父进程和子进程都独立执行。 pclose()不是'kill'函数,它只是等待子进程终止。由于它是一个阻塞函数,执行pclose()期间生成的输出数据可能会丢失。

为了避免这些数据丢失,只有当我们知道子进程已经终止时才会调用pclose():fgets()调用将返回NULL或fread()从阻塞返回,共享流到达结尾,EOF()将返回true。

这是一个使用popen()和fread()的例子。如果执行进程失败,则此函数返回-1,如果执行,则返回0。子输出数据在szResult中返回。

popen

注意,返回流上的所有文件操作都在BLOCKING模式下工作,流是打开的,没有O_NONBLOCK标志。当子进程挂起并且神经终止时,fread()可以永久阻止,因此只能使用popen()和可信程序。

要对子进程采取更多控制并避免文件阻塞操作,我们应该自己使用fork / vfork / execlv等,修改用O_NONBLOCK标志打开的管道,不时使用poll()或select()确定是否有一些数据然后使用read()函数从管道读取。

定期使用带有WNOHANG的waitpid()来查看子进程是否已终止。

© www.soinside.com 2019 - 2024. All rights reserved.