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;
}
我想问:
- 有没有可能
close(0)
先于dup(pfds[1])
执行?如果是,那么在那种情况下程序将不会按预期运行。
下面这几行代码有什么用:
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])
绝对有必要保证程序的正确性。
以下代码是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;
}
我想问:
- 有没有可能
close(0)
先于dup(pfds[1])
执行?如果是,那么在那种情况下程序将不会按预期运行。 下面这几行代码有什么用:
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])
绝对有必要保证程序的正确性。