41

当使用管道进行进程间通信时,关闭管道一端的目的是什么?

例如:如何使用管道在两个程序之间发送一个简单的字符串?

请注意,管道的一侧在子进程和父进程中是关闭的。为什么需要这个?

4

3 回答 3

62

如果您使用管道连接两个进程(父进程和子进程),则在分叉之前创建管道。

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

如果它注意到 EOF 条件,阅读端应该知道作者已经完成。这只有在所有写入面都关闭时才会发生。因此,最好尽快关闭其写入 FD。

writer 应该关闭它的读取 FD,只是为了不让太多的 FD 打开,从而达到可能存在的打开 FD 的限制。此外,如果当时唯一的读者死了,作者会通过收到一个 SIGPIPE 或至少一个 EPIPE 错误(取决于信号的定义方式)得到通知。如果有多个阅读器,作者无法检测到“真正的阅读器”消失了,继续写入并在写入 FD 阻塞时卡住,希望“未使用”的阅读器会读到一些东西。

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

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

    然后

    • 父母应该关闭它的阅读端(为了不浪费FD和正确检测垂死的阅读器)和
    • 孩子必须关闭其书写端(以便能够检测到 EOF 条件)。
于 2013-10-09T07:10:43.833 回答
10

在给定时间可以打开的文件描述符的数量是有限的。如果您一直打开管道而不是很快关闭它们,您将用完 FD 并且无法再打开任何东西:不是管道,不是文件,不是套接字,......

关闭管道很重要的另一个原因是关闭本身对应用程序有意义。例如,管道的一个常见用途是在使用和启动外部程序时将errno子进程发送到父进程:forkexec

  1. 父进程创建管道,调用fork创建子进程,关闭其写入端,并尝试从管道读取。
  2. 子进程尝试使用exec运行不同的程序:
    1. 如果exec失败,例如因为程序不存在,子进程写入errno管道,父进程读取它并知道出了什么问题,并可以告诉用户。
    2. 如果exec成功,则关闭管道而不写入任何内容。父函数返回 0 表示管道已read关闭并且知道程序已成功启动。

如果父级在尝试从管道读取之前没有关闭其管道的写入端,这将不起作用,因为函数在成功read时永远不会返回。exec

于 2013-10-09T07:06:46.597 回答
7

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

~ Micheal Kerrisk,Linux 编程接口

于 2020-05-03T12:02:14.373 回答