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

发布于 2024-08-11 01:53:11 字数 895 浏览 16 评论 0原文

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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

岛歌少女 2024-08-18 01:53:11

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.

夏末染殇 2024-08-18 01:53:11

到目前为止,我对答案的普遍性感到失望(我可以 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 版本中的相关调用堆栈,我用有关正在发生的事情的注释对其进行了注释:

// 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() 它的文件描述符,因为它会阻塞,直到子进程终止。当然,在此之后,您将在管道中留下一个文件描述符,其中将包含子进程在终止之前设法 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 calls fclose() with no additional effect, so the 2 calls are same. In fact you could use pclose() and fclose() interchangeably. I'm sure this is purely a coincidence in the evolved implementation, and the use of pclose() to close a FILE * returned from popen() 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 as fseek(), fread(), and of relevance fclose(). When calling popen(), a different jump table used than the one used by fopen(). The close member in this jump table points to a special function _IO_new_proc_close, which calls waitpid() on the pid stored in the region pointed to by FILE *.

Here's the relevant call stack in my version of glibc, which I've annotated with notes about what is going on:

// 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

So the short of it is, using popen(), the returned FILE * must not be closed, even if you dup() 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 from popen(), the underlying pipe will not be touched, it's safe to use the file descriptor by fileno(), and finish up by calling pclose().

第七度阳光i 2024-08-18 01:53:11

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

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

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

针对你的第二个问题,我认为你可以使用 fileno() 返回的描述符。无需dup()它。完成后,pclose() 原来的。

The FILE* returned by popen() should be closed by pclose(), not by fclose(). Then, the documentation for pclose() specifies:

The pclose() function waits for the
associated process to terminate and
returns the exit status of the command
as returned by wait4().

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 to dup() it. After you're done with it, pclose() the original.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文