beej导流管实例讲解

beej guide pipe example explanation

以下代码是beej的指南中给出的管道实现:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(void)
{
 int pfds[2];
 pipe(pfds);
 if (!fork()) {
 close(1); /* close normal stdout */
 dup(pfds[1]); /* make stdout same as pfds[1] */
 close(pfds[0]); /* we don't need this */
 execlp("ls", "ls", NULL);
 } else {
 close(0); /* close normal stdin */
 dup(pfds[0]); /* make stdin same as pfds[0] */
 close(pfds[1]); /* we don't need this */
 execlp("wc", "wc", "-l", NULL);
 }
 return 0;
}

我想问:

  1. 有没有可能close(0)先于dup(pfds[1])执行?如果是,那么在那种情况下程序将不会按预期运行。
  2. 下面这几行代码有什么用:

    close(pfds[0]); /* we don't need this */
    close(pfds[1]); /* we don't need this */
    

如果没有这些行,会有什么变化?

问题一: 是的,"close(0);"(在 parent 中)完全有可能在 "dup(pfds[1]);"(在 child 中)之前执行。但由于这发生在不同的进程中,child 仍将打开 fd 0。

问题二: 关闭进程不会使用的管道末端是一种很好的簿记做法。这样,您就可以在更复杂的程序中进一步避免错误。在上面的场景中,child 进程应该只从管道中读取。如果在 child 中关闭写入端,则任何尝试写入它都会导致错误,否则您可能会遇到难以检测的错误。

Is it possible that close(0) is executed before dup(pfds[1])? If yes, then in that case the program will not behave as expected.

是的,可以在 child 调用 dup(pfds[1]) 之前让 parent 成功完成 close(0)。但是,这不是问题。当你 fork 一个新进程时,新进程会获得 parent 内存地址 space 的完整副本,包括打开的文件描述符(标有 O_CLOEXEC 标志的除外 - 请参阅 fcntl(2)).因此,基本上每个进程都有自己的文件描述符私有副本,并且是隔离的,可以自由地使用该副本做任何它想做的事情。

因此,当 parent 调用 close(0) 时,它只是关闭其文件描述符 0 (stdin) 的副本;它不会以任何方式影响 child,它仍然引用 stdin 并且可以在需要时使用它(即使在这个例子中它不会)。

What is the use of the following lines of code:

close(pfds[0]); /* we don't need this */
close(pfds[1]); /* we don't need this */

最佳实践要求您关闭不使用的文件描述符 - close(pfds[0]) 就是这种情况。未使用的打开文件描述符会占用 space 和资源,如果您不打算使用它,为什么要保持打开状态?

close(pfds[1]) 虽然有点微妙。只有当管道缓冲区中没有更多数据时,管道才会报告文件结束 并且 没有活动的编写器,即没有打开管道进行写入的实时进程。如果你不在parent中关闭pfds[1],程序将永远挂起,因为wc(1)永远不会看到输入的结束,因为有一个进程(wc(1)本身)已打开管道进行写入,因此可以(但不会)写入更多数据。

Tl;DR:close(pfds[0]) 只是一种好的做法,但不是强制性的; close(pfds[1]) 绝对有必要保证程序的正确性。