我一直在阅读有关未命名管道的信息,据我了解,它们是作为内存中的缓冲区实现的。创建管道时,我需要传递一个大小为 2 的数组,它返回两个指向缓冲区的指针(文件描述符)。索引 0 用于从管道读取,索引 1 用于写入。
我的问题是,如果缓冲区只有一个并且两个索引都指向同一内存位置并且两个进程无法同时读写,那么为什么我需要两个文件描述符?我希望我的问题有意义。
答案1
正如 @MelBoyce 在他的评论中所述,这是因为管道的概念性质。它有一个输入和一个输出,您从输出中读取字节的顺序与写入输入中的顺序完全相同。管道不是常见的文件或指针,您不应该在其中的任何位置进行读写。您被迫读取进入管道的第一个尚未读取的字节。
管道可以实现为内存中的缓冲区,但如果将来发明了另一种更有效的方法,则实现可能会有所不同。然而,管道的概念性质不会改变。您仍将使用相同的系统调用read(2)
,write(2)
并且它们的行为仍然相同。调用时获得的文件描述符pipe(2)
用于强制您正确使用管道(并另外提供一些访问控制)。这样,将来对实现的修改就不会破坏您的代码。
答案2
如果返回用于读取和写入的单个文件描述符,则将缺少一个重要功能pipe
:将无法发出 EOF 信号。
对于管道来说,当写入 fd 的最后一个副本关闭时,读取 fd 会看到 EOF。对于假设的单FD管道,您需要一个额外的系统调用,就像shutdown
套接字一样,但应用于管道。 (请记住,管道比套接字更古老,并且shutdown
在套接字之前并不存在。)
谁负责调用这个pipe_shutdown()
系统调用?假设你这样做:
grep foo /etc/passwd | head
如果只有一个 fd,grep
并且head
两者都继承了,那么在grep
退出时,head
需要接收一个 EOF。但是进程持有的 fdhead
是可写的,因此内核不确定是否不会再向管道写入任何内容。您可能会想:进程退出应该自动在进程持有的所有管道 fd 上发送 EOF。但这会破坏很多东西,比如
( echo FOO ; grep foo /etc/passwd ) | more
echo
写入管道并退出,我们还不希望发生 EOF。grep
仍然会写入管道。这个问题不仅限于带括号的子 shell 的“模糊”情况。任何时候在管道左侧使用 shell 脚本时都可能发生这种情况。
如果您仔细考虑一下,管道 EOF 机制确实需要区分可以写入管道的 fd 和只能读取管道的 fd。这并不取决于涉及多少进程。