在Linux中为什么要关闭一个管道?

45
3个回答

72

如果你使用管道将两个进程 - 父进程和子进程 - 连接起来,那么在fork之前需要创建管道。

fork使得两个进程都可以访问管道的两端口,这是不可取的。

读取端口应该在它注意到EOF条件时学会写入者已经完成。只有当所有写入端口关闭时才能发生这种情况。因此最好尽快关闭它的写入FD。

为了避免打开太多的FD(文件描述符),作者应该关闭其读取FD,并且不要达到可能存在的打开FD数量的限制。此外,如果仅剩的读取器死亡,则通过获得SIGPIPE或至少是EPIPE错误(取决于信号如何定义)来通知作者。如果有多个读者,作者无法检测到"真正的读者"走了,它继续写入并被阻塞,希望“未使用”的读取器将读取一些东西。

所以这里详细说明了发生了什么:

  • 父进程调用pipe()并获得2个文件描述符:我们称它为rdwr
  • 父进程调用fork()。现在两个进程都有一个rd和一个wr
  • 假设子进程是读取者。

    那么:

    • 父进程应该关闭其读取端口(以免浪费FD并进行适当的死亡读取者检测);
    • 子进程必须关闭其写入端口(为了能够检测EOF条件)。

"每个已经存在的读取器都应该读取其数据" - 我认为这并不正确。如果一个管道有两个读取器p1和p2,并且p1从管道中读取了一些字节,那么p2中的read()将获得下一组字节。 - 在我看来,所有其他论点都是正确的,特别是EOF检测。 - Martin R
@MartinR 嗯...我觉得你是对的。我会改变我的答案。 - glglgl
类似于EOF检测的论点也可以应用于写入方向:如果只在子进程中打开读取端,并且子进程死亡,则向管道写入会导致EPIPE(或返回错误)。如果在父进程中还额外打开了读取端,则此方法将无效。因此,这确实是一个对称的论点。 - Martin R
@MartinR你再次说得对。如果“真正的读者”关闭,只留下“未使用的”读者,那么写入端并不会注意到这一点,继续写入。最终写入端会阻塞,我们又束手无策了。所以需要进行另一次编辑。 - glglgl
1
@yangmillstheory 是的,在这种情况下,你可能不需要它。 - glglgl
显示剩余6条评论

11
文件描述符的数量是有限制的。如果您不断打开管道而不及时关闭它们,您将会耗尽FD并且无法再打开任何东西:既不能打开管道,也不能打开文件,还不能打开套接字等等。
另一个需要关闭管道的重要原因是关闭本身对应用程序具有意义。例如,使用fork和exec启动外部程序时,常见的管道用途是将子进程中的errno发送到父进程中:了解更多
  1. 父进程创建管道,调用fork创建子进程,关闭其写入端口,并尝试从管道中读取。
  2. 子进程尝试使用exec运行不同的程序:
    1. 如果exec失败,例如因为程序不存在,子进程将errno写入管道,父进程读取它并知道出了什么问题,并可以告诉用户。
    2. 如果exec成功,则在不写入任何内容的情况下关闭管道。父进程中的read函数返回0,表示管道已关闭,并且知道程序已成功启动。

如果父进程在尝试从管道中读取之前没有关闭其写入端口,则此过程将无法正常工作,因为当exec成功时,read函数将永远不会返回。


8
关闭未使用的管道文件描述符不仅仅是为了确保进程不会耗尽其有限的文件描述符集合,而且对于正确使用管道至关重要。现在我们考虑为什么必须关闭读端和写端的未使用文件描述符。
从管道读取的进程关闭其写端的管道描述符,以便当另一个进程完成其输出并关闭其写端描述符时,读取端可以看到文件结束标志(一旦它已经准备好从管道中读取任何未完成的数据)。
如果读取进程不关闭管道的写端,则在另一个进程关闭管道的写端描述符后,读取进程即使读取了管道中的所有数据也不会看到文件结束标志。相反,read() 将阻塞等待数据,因为内核知道该管道仍然至少有一个写入描述符处于打开状态。这个描述符由读取进程自身保持打开状态是无关紧要的。理论上,该进程仍然可以向管道中写入数据,即使它正在尝试读取并被阻塞。
例如,read() 可能会被信号处理程序中断,并向管道写入数据。
写入进程关闭其读取管道的描述符有另一个原因。
当进程尝试向没有任何进程打开读取描述符的管道写入时,内核向写入进程发送 SIGPIPE 信号。默认情况下,此信号会杀死进程。进程可以安排捕获或忽略此信号,在这种情况下,管道上的 write() 将失败,并显示错误 EPIPE(断开的管道)。接收到 SIGPIPE 信号或获取 EPIPE 错误是有关管道状态的有用指示,这就是为什么应该关闭未使用的管道读取描述符的原因。
如果写入进程不关闭管道的读取端,则即使在另一个进程关闭了管道的读取端之后,写入进程仍将填充该管道,进一步尝试写入将无限期地阻塞。
关闭未使用文件描述符的最后一个原因是只有在所有文件描述符都关闭之后,管道才被销毁,其资源才能释放以供其他进程重用。此时,管道中的任何未读数据都会丢失。
~ Micheal Kerrisk,Linux编程接口

3
若将这个回答分段,那么它会更好。 - Daniel Pop

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接