3

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

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

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

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(),但是fclose()也有块。此外,我在 glibc 代码中四处寻找,然后pclose()只调用fclose(). fclose()无论使用或使用,行为都是相同pclose()的。

4

3 回答 3

9

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

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

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

如果你 fork 和 exec 并自己做剩下的事情,你最终不会(直接或间接地)调用 pclose(),所以在关闭时间没有等待。但是请注意,除非您的程序设置为忽略 SIGCHLD,否则您的进程不会终止(而是会变成僵尸),直到子进程终止。但至少你的成本会先退出。

于 2009-11-16T04:50:29.230 回答
8

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

在 glibc中pclose()直接调用fclose()没有额外的效果,所以 2 调用是相同的。事实上,你可以使用pclose()fclose()互换。我确信这纯粹是进化实现中的巧合,仍然建议使用pclose()关闭FILE *返回的 from 。popen()

魔法就在popen()FILE *glibc 中的 s 包含一个跳转表,其中包含指向适当函数的指针,以处理诸如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()

于 2009-11-20T10:44:37.790 回答
1

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

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

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

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

于 2009-11-15T11:55:22.333 回答