以下代码是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 */
如果不存在这些线条会有什么变化?
答案 0 :(得分:1)
是否可能在dup(pfds [1])之前执行close(0)?如是, 那么在这种情况下,程序将不会按预期运行。
是的,在孩子拨打close(0)
之前,父母可以成功完成dup(pfds[1])
。但是,这不是问题。分叉新进程时,新进程将获取父进程内存地址空间的完整副本,包括打开的文件描述符(标有O_CLOEXEC
标记的那些除外 - 请参阅fcntl(2)
)。因此,基本上每个进程都有自己的文件描述符的私有副本,并且是孤立的,可以随意使用该副本执行任何操作。
因此,当父调用close(0)
时,它只关闭文件描述符0(stdin
)的副本;它不会以任何方式影响孩子,它仍然引用stdin
并且可以在需要时使用它(即使在这个例子中它也不会)。
以下代码行的用途是什么:
close(pfds[0]); /* we don't need this */
close(pfds[1]); /* we don't need this */
最佳做法要求您应关闭不使用的文件描述符 - close(pfds[0])
就是这种情况。未使用的打开文件描述符占用了空间和资源,如果你不打算使用它,为什么要保持开放呢?
close(pfds[1])
虽然更加微妙。只有当管道缓冲区中没有更多数据并且没有活动写入器时,管道才报告文件结束,即没有打开管道进行写入的实时进程。如果您没有在父级中关闭pfds[1]
,程序将永久挂起,因为wc(1)
将永远不会看到输入的结束,因为有一个进程(wc(1)
本身)具有管道开放写作,因此可以(但不会)写更多数据。
Tl; DR:close(pfds[0])
只是一种好习惯,但不是强制性的; close(pfds[1])
对于确保程序的正确性是绝对必要的。
答案 1 :(得分:-1)
问题1: 是的,完全有可能“关闭(0);” (在父级中)在“dup(pfds [1]);”之前执行(在孩子身上)。但由于这种情况发生在不同的过程中,孩子仍然会打开fd 0。
问题2: 关闭流程不会使用的管道末端是一种很好的簿记操作。这样,您可以在更复杂的程序中避免进一步的错误。在上面的场景中,子进程应该只读取管道。如果你关闭了孩子的写端,eny尝试写入它会导致错误,否则你可能会有一个难以检测的错误。