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

Is output read from popen()ed FILE* complete before pclose()?

pclose() 的手册页说:

The pclose() function waits for the associated process to terminate and returns the exit status of the command as returned by wait4(2).

我觉得这意味着如果 popen() 创建的关联 FILE* 以类型 "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 无效之后,那么我们就无法再从任何地方检索命令的输出了,对吗?

这感觉像是先有鸡还是先有蛋的问题,但我已经看到了与上述类似的代码,所以我可能误解了什么。感谢您对此的解释。

仔细阅读the documentation for popen

The pclose() function shall close a stream that was opened by popen(), wait for the command to terminate, and return the termination status of the process that was running the command language interpreter.

它阻塞并等待。

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

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

为避免此数据丢失,我们将仅在知道子进程已终止时才调用 pclose():fgets() 调用将从 return NULL 或 fread() return阻塞,共享流到达末尾并且 EOF() 将 return true.

这里是一个使用 popen() 和 fread() 的例子。此函数 return 如果执行过程失败则为 -1,如果正常则为 0。子输出数据是return in szResult.

int exec_command( const char * szCmd, std::string & szResult ){

    printf("Execute commande : [%s]\n", szCmd );

    FILE * pFile = popen( szCmd, "r");
    if(!pFile){
            printf("Execute commande : [%s] FAILED !\n", szCmd );
            return -1;
    }

    char buf[256];

    //check if the output stream is ended.
    while( !feof(pFile) ){

        //try to read 255 bytes from the stream, this operation is BLOCKING ...
        int nRead = fread(buf, 1, 255, pFile);

        //there are something or nothing to read because the stream is closed or the program catch an error signal
        if( nRead > 0 ){
            buf[nRead] = '[=10=]';
            szResult += buf;
        }
    }

    //the child process is already terminated. Clean it up or we have an other zoombie in the process table.
    pclose(pFile); 

    printf("Exec command [%s] return : \n[%s]\n",  szCmd, szResult.c_str() );
    return 0;
}

请注意,return 流上的所有文件操作都在 BLOCKING 模式下工作,流在没有 O_NONBLOCK 标志的情况下打开。当子进程挂起和 nerver 终止时,fread() 可以永远被阻塞,所以只对受信任的程序使用 popen()。

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

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

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

本质上:是的,从 popen 返回的 FILE*pclose 之前的 fread 是安全的。假设提供给 fread 的缓冲区足够大,您将不会 "miss" 提供给 popencommand 生成的输出。

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

感谢 C - pipe without using popen,我更好地理解了 popen 背后的作用:它执行 dup2 将其 stdout 重定向到它使用的管道。重要的是:它执行某种形式的 exec 以在派生进程中执行指定的 command 并且在该子进程终止后,其打开的文件描述符,包括 1 ( stdout) 已关闭。 IE。指定 command 的终止是子进程 stdout 关闭的条件。

接下来,我回过头来更仔细地思考 EOF 在这种情况下到底是什么。起初,我有一种松散的错误印象,即“fread 尝试尽可能快地从 FILE* 中读取,并在最后一个字节后 returns/unblocks已读取”。这不完全正确:如上所述:fread 将 read/block 直到读取其目标字节数或 EOF 或遇到错误。 popen返回的FILE*来自popen使用的管道读端的fdopen,所以它的EOF出现当子进程' stdout - dup2 与管道的写入端一起 - 关闭时.

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

command 的完整输出

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

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

当 child 进程关闭其管道末端时,您会得到一个 EOF。当它显式调用 close 退出时,可能会发生这种情况。在那之后,你的管子末端什么都不能出来。收到 EOF 后,您不知道进程是否已终止,但您可以肯定它永远不会向管道写入任何内容。

通过调用 pclose 您可以关闭管道的末端 并且 等待 child 的终止。当pclosereturns时,就知道child已经终止了。

如果你在没有得到 EOF 的情况下调用 pclose,并且 child 试图将内容写入管道的末端,它将失败(实际上它会得到一个 SIGPIPE 可能会死)。

这里绝对没有任何 chicken-and-egg 情况的余地。