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
无效,那么我们就无处可以检索命令的输出了,对吧?
这感觉就像一个鸡蛋问题,但我看到的代码与上面的代码相似,所以我可能误解了一些东西。我很感激对此的解释。
TL; DR执行摘要:我们如何知道用fread()检索的内容是否完整? - 我们有一个EOF。
当子进程关闭管道末端时,您将获得EOF。当它明确地调用close
或退出时会发生这种情况。在此之后,没有任何东西可以从管道的末端出来。获得EOF后,您不知道该进程是否已终止,但您确实知道它永远不会向管道写入任何内容。
通过调用pclose
,您关闭管道的末端并等待终止孩子。当pclose
回来时,你知道孩子已经终止了。
如果你在没有获得EOF的情况下调用pclose
,并且孩子试图将东西写入管道的末端,那么它将失败(事实上它将获得一个SIGPIPE
并且可能会死亡)。
这里鸡蛋和鸡蛋的情况绝对没有空间。
我在进一步研究这个问题时学到了一些东西,我想回答我的问题:
基本上:是的,fread
在FILE*
之前从popen
返回的pclose
是安全的。假设给fread
的缓冲区足够大,你不会“错过”给command
的popen
生成的输出。
回过头来仔细考虑fread
的作用:它有效地阻塞,直到(size
* nmemb
)字节被读取或遇到文件结束(或错误)。
感谢C - pipe without using popen,我更了解popen
在引擎盖下做了什么:它有一个dup2
将其stdout
重定向到它使用的管道的写端。重要的是:它执行某种形式的exec
来在分叉进程中执行指定的command
,并且在此子进程终止后,其打开的文件描述符(包括1
(stdout
))将被关闭。即终止指定的command
是儿童进程'stdout
关闭的条件。
接下来,我回过头来仔细考虑EOF
在这种背景下的真正含义。起初,我处于松散的错误和错误的印象,“fread
试图尽可能快地从FILE*
读取并在读取最后一个字节后返回/解除阻塞”。这不是真的:如上所述:fread
将读取/阻塞,直到读取其目标字节数或遇到EOF
或错误。由FILE*
返回的popen
来自fdopen
使用的管道读取端的popen
,所以当孩子处理'EOF
- 与管道的写端结合的stdout
- 被关闭时,它发生dup2
。
所以,最后我们得到的是:popen
创建一个管道,其写入端获取运行指定command
的子进程的输出,如果fdopen
ed传递给FILE*
,则其读取结束。 (假设fread
的缓冲区足够大),fread
将阻塞直到fread
发生,这对应于EOF
管道写入端的关闭,这是由执行的popen
终止造成的。即因为command
阻塞直到遇到fread
,并且EOF
发生在EOF
之后 - 在command
的子进程中运行 - 终止,使用fread(具有足够大的缓冲区)来捕获给予popen
的command
的完整输出是安全的。
如果有人能够验证我的推论和结论,我会感激不尽。
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()来查看子进程是否已终止。