从 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" 提供给 popen
的 command
生成的输出。
回过头来仔细考虑 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
,其读取端如果 fdopen
ed 到 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 的终止。当pclose
returns时,就知道child已经终止了。
如果你在没有得到 EOF 的情况下调用 pclose
,并且 child 试图将内容写入管道的末端,它将失败(实际上它会得到一个 SIGPIPE
可能会死)。
这里绝对没有任何 chicken-and-egg 情况的余地。
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 bypopen()
, 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" 提供给 popen
的 command
生成的输出。
回过头来仔细考虑 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
,其读取端如果 fdopen
ed 到 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 的终止。当pclose
returns时,就知道child已经终止了。
如果你在没有得到 EOF 的情况下调用 pclose
,并且 child 试图将内容写入管道的末端,它将失败(实际上它会得到一个 SIGPIPE
可能会死)。
这里绝对没有任何 chicken-and-egg 情况的余地。