fclose()/ pclose()可能会阻塞某些文件指针

时间:2009-11-15 08:25:12

标签: c unix popen stdio fclose

fclose()其文件描述符块之后调用dup(),直到子进程结束(可能是因为流已经结束)。

FILE *f = popen("./output", "r");
int d = dup(fileno(f));
fclose(f);

但是,通过手动执行pipe()的{​​{1}},fork()execvp(),然后popen()管道的读取文件描述符,关闭原始文件不会阻止。

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]); } } 返回的FILE *并在其位置使用文件描述符?

更新

我知道文档说要使用popen(),但pclose()也会阻止。此外,我在glibc代码中搜索过,fclose()只调用pclose()。无论使用fclose()还是fclose(),行为都是相同的。

3 个答案:

答案 0 :(得分:9)

http://linux.die.net/man/3/popen

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

由于pclose()想要返回退出状态,它必须等待子进程终止并生成一个。由于fclose()调用了pclose(),因此同样适用于fclose()。

如果你自己分叉并执行其余的操作,那么你最终不会直接或间接地调用pclose(),所以在关闭时不会等待。但请注意,除非您的程序设置为忽略SIGCHLD,否则您的进程将不会终止(相反,它会变为僵尸),直到该子进程为止。但至少你的成本会先退出。

答案 1 :(得分:8)

对目前为止的答案的普遍性感到失望(我可以RTFM,tyvm),我通过逐步阅读glibc来源彻底调查了这一点。

在glibc中pclose()直接调用fclose()而没有其他影响,因此2次调用是相同的。实际上,您可以互换使用pclose()fclose()。我确信这在演进的实现中纯粹是巧合,并且仍然建议使用pclose()来关闭从FILE *返回的popen()

魔法在popen()。 glibc中的FILE *包含一个跳转表,其中包含指向适当函数的指针,以处理fseek()fread()和相关fclose()等调用。调用popen()时,使用的跳转表与fopen()使用的跳转表不同。此跳转表中的close成员指向一个特殊函数_IO_new_proc_close,该函数调用存储在waitpid()指向的区域中的pid上的FILE *

这是我的glibc版本中的相关调用堆栈,我已经注释了有关正在发生的事情的注释:

// linux waitpid system call interface
#0  0x00f9a422 in __kernel_vsyscall ()
#1  0x00c38513 in __waitpid_nocancel () from /lib/tls/i686/cmov/libc.so.6

// removes fp from a chain of proc files
// and waits for the process of the stored pid to terminate
#2  0x00bff248 in _IO_new_proc_close (fp=0x804b008) at iopopen.c:357

// flushes the stream and calls close in its jump table
#3  0x00c09ff3 in _IO_new_file_close_it (fp=0x804b008) at fileops.c:175

// destroys the FILEs buffers
#4  0x00bfd548 in _IO_new_fclose (fp=0x804b008) at iofclose.c:62

// calls fclose
#5  0x00c017fd in __new_pclose (fp=0x804b008) at pclose.c:43

// calls pclose
#6  0x08048788 in main () at opener.c:34

所以缺点是,使用popen(),即使您FILE *其文件描述符,也不能关闭返回的dup(),因为它将阻塞,直到子进程终止。当然,在此之后你将留下一个文件描述符给一个管道,它将包含子进程在终止之前设法写入的所有内容。

fread()使用popen()返回的文件指针,不会触及底层管道,fileno()使用文件描述符是安全的,并通过调用完成pclose()

答案 2 :(得分:1)

FILE*返回的popen()应由pclose()关闭,而不是由fclose()关闭。然后,pclose()的文档指定:

  

pclose()函数等待   终止和关联的过程   返回命令的退出状态   由wait4()返回。

因此,除了关闭文件描述符之外,等待是pclose()做的两件事之一。 close()只做一件事:它关闭描述符。

在回答您的第二个问题时,我认为您可以使用fileno()返回的描述符。没有dup()它。完成后,pclose()原始文件。