【问题标题】:Why should you close a pipe in linux?为什么要在 linux 中关闭管道?
【发布时间】:2013-10-16 09:48:02
【问题描述】:

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

例如:How to send a simple string between two programs using pipes?

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

【问题讨论】:

    标签: c linux pipe


    【解决方案1】:

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

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

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

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

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

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

      然后

      • 父母应关闭其阅读端(为了不浪费 FD 并正确检测垂死的阅读器)和
      • 孩子必须关闭其书写端(以便能够检测到 EOF 条件)。

    【讨论】:

    • “每个现有的阅读器都应该阅读其数据” - 我认为这是不正确的。如果管道有两个读取器 p1 和 p2,并且 p1 从管道读取一些字节,那么 p2 中的 read() 将获取下一个字节。 - 我认为所有其他论点都是正确的,尤其是 EOF 检测。
    • @MartinR 嗯...我认为你是对的。我会改变我的答案。
    • 类似的EOF检测参数也可以应用于写入方向:如果读取端only在子进程中打开并且子进程死亡,则写入管道会产生 EPIPE(或返回错误)。如果读取端在父进程中另外打开,这将不起作用。所以它确实是一个对称的论点。
    • @MartinR 你又是对的。如果“真正的读者”死了,留下了“未使用的”读者,作者不会注意到这一点,继续写作。写入 FD 最终会阻塞,我们无法再做任何事情。所以另一个编辑。
    • @yangmillstheory 是的,在这种情况下你可能不需要它。
    【解决方案2】:

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

    关闭管道很重要的另一个原因是关闭本身对应用程序有意义。例如,管道的一个常见用法是在使用forkexeclaunch an external program 时将errno 从子进程发送到父进程:

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

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

    【讨论】:

      【解决方案3】:

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

      ~ Micheal Kerrisk,Linux 编程接口

      【讨论】:

      • 这将是一个很好的答案,如果它被吐在段落中
      猜你喜欢
      • 2021-04-04
      • 2010-11-01
      • 1970-01-01
      • 1970-01-01
      • 2019-12-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多