【问题标题】:fclose()/pclose() may block on some file pointersfclose()/pclose() 可能会阻塞某些文件指针
【发布时间】:2010-12-16 18:10:21
【问题描述】:

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

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

但是通过手动执行popen()pipe()fork()execvp(),然后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]);
    }
}

为什么会发生这种情况,如何关闭从popen() 返回的FILE * 并使用文件描述符代替它?

更新:

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

【问题讨论】:

  • 你知道为什么从popen() 块中关闭FILE* 的唯一定义方法。也许您可以多解释一下您的问题的哪一部分仍未得到解答?

标签: c unix popen stdio fclose


【解决方案1】:

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

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

由于 pclose() 想要返回退出状态,它必须等待子进程终止并生成一个。由于 fclose() 调用 pclose(),因此对您来说 fclose() 也是如此。

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

【讨论】:

  • pclose() 调用fclose(),而不是相反。
  • 无论如何。在您的情况下,两者最终都会导致等待。因为据我所知,在 UNIX 中,FILE * 实际上指向函数指针数组。显然,来自 popen() 的 FILE * 附近的函数指针等待子进程完成,以便它可以获取返回值。
  • +1 为什么会被否决?它是正确的、有帮助的、中肯的
  • 我无法通过错误的调用顺序,并想了解为什么 pclose()fclose() 都被阻止,而不是手册页中的一些文本。
【解决方案2】:

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

在glibc中pclose()直接调用fclose()没有附加效果,所以2个调用是一样的。事实上,您可以交替使用pclose()fclose()。我确信这纯粹是进化实现中的巧合,仍然建议使用pclose() 关闭从popen() 返回的FILE *

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

这是我的 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()ing 与从popen() 返回的文件指针,将不会触及底层管道,使用fileno() 的文件描述符是安全的,并通过调用pclose() 完成。

【讨论】:

  • 我上面提到了代码指针数组。它不只是 glibc,在每个 UNIX 标准 C 库中一直都是这样。多么奇怪,你可以为自己的问题写一个答案并接受它作为答案?哇,每个人的即时业力。
  • 如果你知道这一点,你可以回答这么多。
【解决方案3】:

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

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

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

针对您的第二个问题,我认为您可以使用fileno() 返回的描述符。没有必要dup() 它。完成后,pclose() 原版。

【讨论】:

  • @Andomar:我知道文档说要使用 pclose(),但是 fclose() 也会阻止。此外,我查看了 libc 代码,pclose() 只是调用了fclose()
  • @Anacrolix:从popen()FILE* 调用fclose() 是未定义的行为。您的 libc 可以做任何它喜欢的事情:阻止、打印错误消息或格式化您的磁盘
  • @Andomar,我很清楚这一点。我使用fclose()/pclose() 比较来指出等待行为似乎是fclose() 调用所特有的。
猜你喜欢
  • 2012-12-20
  • 1970-01-01
  • 1970-01-01
  • 2015-01-18
  • 2020-06-17
  • 2021-07-25
  • 2022-01-11
  • 1970-01-01
  • 2020-10-24
相关资源
最近更新 更多