fclose()/pclose() 可能会阻塞某些文件指针
在 dup()
其文件描述符之后调用 fclose()
会阻塞,直到子进程结束(可能是因为流已结束)。
FILE *f = popen("./output", "r");
int d = dup(fileno(f));
fclose(f);
但是,通过手动执行 popen()
的 pipe()
、fork()
、execvp()
,然后使用 dup() 处理管道的读取文件描述符,关闭原始文件描述符不会阻塞。
int p[2];
pipe(p);
switch (fork()) {
case 0: {
char *argv[] = {"./output", NULL};
close(p[0]);
dup2(p[1], 1);
execvp(*argv, argv);
}
default: {
close(p[1]);
int d = dup(p[0]);
close(p[0]);
}
}
为什么会发生这种情况?如何关闭从 popen()
返回的 FILE *
并在其位置使用文件描述符?
更新:
我知道文档说要使用pclose()
,但是fclose()
也会阻止。此外,我查看了 glibc 代码,pclose() 只调用了 fclose() 。无论使用 fclose()
还是 pclose()
,行为都是相同的。
Calling fclose()
here after dup()
ing its file descriptor blocks until the child process has ended (presumably because the stream has ended).
FILE *f = popen("./output", "r");
int d = dup(fileno(f));
fclose(f);
However by manually performing the pipe()
, fork()
, execvp()
of the popen()
, and then dup()
ing the pipe's read file descriptor, closing the original does not block.
int p[2];
pipe(p);
switch (fork()) {
case 0: {
char *argv[] = {"./output", NULL};
close(p[0]);
dup2(p[1], 1);
execvp(*argv, argv);
}
default: {
close(p[1]);
int d = dup(p[0]);
close(p[0]);
}
}
Why does this occur, and how can I close the FILE *
returned from popen()
and use a file descriptor in its place?
Update:
I'm aware that the documentation says to use pclose()
, however fclose()
blocks as well. Furthermore, I poked around in the glibc code, and pclose()
just calls fclose()
. The behaviour is the same, whether fclose()
or pclose()
is used.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
http://linux.die.net/man/3/popen
pclose() 函数等待关联进程终止,并返回 wait4() 返回的命令的退出状态。
由于 pclose() 想要返回退出状态,因此它必须等待子进程终止并生成一个。由于 fclose() 调用 pclose(),因此这同样适用于 fclose()。
如果您 fork 和 exec 并自己完成其余的工作,您最终不会调用 pclose() (直接或间接),因此在关闭时无需等待。但请注意,除非您的程序设置为忽略 SIGCHLD,否则您的进程不会终止(相反,它会变成僵尸),直到子进程终止。但至少你的成本会先跑出去。
http://linux.die.net/man/3/popen
The pclose() function waits for the associated process to terminate and returns the exit status of the command as returned by wait4().
Since pclose() wants to return the exit status, it has to wait for the child to terminate and generate one. Since fclose() calls pclose(), the same applies to fclose() for you.
If you fork and exec and do the rest yourself, you don't end up calling pclose() (directly or indirectly), so there's no waiting at close time. Note however that unless your program is set to ignore SIGCHLD, your process won't terminate (instead it'll go zombie) until the child does. But at least your cost will run to exit first.
到目前为止,我对答案的普遍性感到失望(我可以 RTFM,tyvm),我进行了调查通过逐步浏览并阅读 glibc 源代码来彻底了解这一点。
在glibc中,
pclose()
直接调用fclose()
,没有额外的效果,所以这两个调用是相同的。事实上,您可以互换使用pclose()
和fclose()
。我确信这纯粹是进化实现中的巧合,以及使用pclose()
关闭从popen()
返回的FILE *
代码>仍然推荐。神奇之处在于
popen()
。 glibc 中的 FILE * 包含一个跳转表,其中包含指向适当函数的指针,以处理诸如fseek()
、fread()
和 of 等调用相关性fclose()
。调用popen()
时,使用的跳转表与fopen()
使用的跳转表不同。此跳转表中的close
成员指向一个特殊函数_IO_new_proc_close
,该函数对存储在所指向区域中的 pid 调用waitpid()
文件*
。这是我的 glibc 版本中的相关调用堆栈,我用有关正在发生的事情的注释对其进行了注释:
所以它的缺点是,使用
popen()
,返回FILE *
一定不能关闭,即使您dup()
它的文件描述符,因为它会阻塞,直到子进程终止。当然,在此之后,您将在管道中留下一个文件描述符,其中将包含子进程在终止之前设法 write() 的任何内容。通过不使用
fread()
从popen()
返回的文件指针,底层管道将不会被触及,通过fileno 使用文件描述符是安全的()
,并通过调用pclose()
完成。Disappointed with the generality of the answers so far (I can RTFM, tyvm), I've investigated this thoroughly, by stepping through and reading the glibc source.
In glibc
pclose()
directly callsfclose()
with no additional effect, so the 2 calls are same. In fact you could usepclose()
andfclose()
interchangeably. I'm sure this is purely a coincidence in the evolved implementation, and the use ofpclose()
to close aFILE *
returned frompopen()
is still recommended.The magic is in
popen()
.FILE *
s in glibc contain a jump table with pointers to appropriate functions to handle such calls asfseek()
,fread()
, and of relevancefclose()
. When callingpopen()
, a different jump table used than the one used byfopen()
. Theclose
member in this jump table points to a special function_IO_new_proc_close
, which callswaitpid()
on the pid stored in the region pointed to byFILE *
.Here's the relevant call stack in my version of glibc, which I've annotated with notes about what is going on:
So the short of it is, using
popen()
, the returnedFILE *
must not be closed, even if youdup()
its file descriptor, because it will block until the child process terminates. Of course, after this you'll be left with a file descriptor to a pipe which will contain whatever the child process managed to write() to it before terminating.By not
fread()
ing with the file pointer returned frompopen()
, the underlying pipe will not be touched, it's safe to use the file descriptor byfileno()
, and finish up by callingpclose()
.popen()
返回的FILE*
应由pclose()
关闭,而不是由fclose()
关闭。然后,pclose()
的文档指定:因此,除了关闭文件描述符之外,等待是 pclose() 所做的两件事之一。
close()
只做一件事:关闭描述符。针对你的第二个问题,我认为你可以使用
fileno()
返回的描述符。无需dup()
它。完成后,pclose()
原来的。The
FILE*
returned bypopen()
should be closed bypclose()
, not byfclose()
. Then, the documentation forpclose()
specifies:So the waiting is one of the two things
pclose()
does, besides closing the file descriptor.close()
does just one thing: it closes the descriptor.In response to your second question, I think you can use the descriptor returned by
fileno()
. There's no need todup()
it. After you're done with it,pclose()
the original.