3

pclose()的手册页说:

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

我觉得这意味着如果用 type 打开关联的FILE*创建以读取的输出,那么直到调用. 但是在 之后,关闭肯定是无效的,那么你怎么能确定你已经阅读了整个输出呢?popen()"r"commandpclose()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无效的,那么我们就无法再从任何地方检索命令的输出了,对吧?

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

4

4 回答 4

3

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

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

通过调用pclose您关闭管道的末端等待孩子的终止。返回时pclose,您知道孩子已终止。

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

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

于 2018-09-07T10:57:06.793 回答
0

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

本质上:是的,fread从之前FILE*返回的返回是安全的。假设给定的缓冲区足够大,您将不会“错过”给定 to生成的输出。popenpclosefreadcommandpopen

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

感谢C-pipe 不使用 popen,我更好地理解popen了幕后的工作:它将其dup2重定向stdout到它使用的管道的写入端。重要的是:它执行某种形式exec来执行command分叉进程中的指定,并且在这个子进程终止后,它的打开文件描述符,包括1( stdout) 被关闭。即指定的终止是关闭command子进程的条件。stdout

接下来,我回过头来更仔细地思考了EOF这个背景下的真实情况。起初,我有一种松散的错误印象,即“尝试尽可能快fread地读取 aFILE*并在读取最后一个字节后返回/解除阻塞”。这并不完全正确:如上所述:fread将读取/阻塞,直到读取其目标字节数或EOF遇到错误。FILE*返回的 by来自popenfdopen使用的管道的读取端popen,因此它EOF 发生在子进程' stdout- 它dup2与管道的写入端一起编辑 - 关闭时

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

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

于 2018-09-07T01:08:40.677 回答
0

更仔细地阅读文档:popen

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

它阻塞并等待。

于 2018-09-06T20:42:09.600 回答
0

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

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

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

这是一个使用 popen() 和 fread() 的示例。如果执行过程失败,此函数返回 -1,如果正常则返回 0。子输出数据在 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] = '\0';
            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;
}

请注意,返回流上的所有文件操作都在 BLOCKING 模式下工作,流是打开的,没有 O_NONBLOCK 标志。当子进程挂起和神经元终止时,fread() 可能会被永远阻塞,因此仅对受信任的程序使用 popen()。

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

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

于 2018-09-06T20:56:56.177 回答