【问题标题】:using EOF for signaling on unnamed pipes使用 EOF 在未命名管道上发出信号
【发布时间】:2011-07-22 12:11:32
【问题描述】:

我有一个测试程序,它使用使用 pipe() 创建的未命名管道在 Linux 系统上使用 fork() 创建的父进程和子进程之间进行通信。

一般情况下,当发送进程关闭管道的write fd时,接收进程从read()返回值为0,表示EOF。

但是,如果我在管道中填充相当大量的数据(在接收器开始读取之前可能是 100K 字节0),则接收器在读取管道中的所有数据后会阻塞 - 即使发送器已关闭它。

我已经验证了发送进程已经用lsof关闭了管道,而且接收方被阻塞的情况似乎很明显。

这导致了一个问题:关闭管道的一端是让接收者知道没有更多数据的可靠方法吗?

如果是这样,并且没有任何条件可以导致 read() 阻塞空的、关闭的 FIFO,那么我的代码就有问题。如果没有,这意味着我需要找到一种替代方法来发出数据流结束的信号。

分辨率

我很确定最初的假设是正确的,关闭管道会导致阅读器端出现 EOF,这个问题只是在黑暗中的一个镜头——我想也许我忽略了一些微妙的管道行为。几乎你见过的每个管道示例都是发送几个字节并退出的玩具。当您不再执行原子操作时,事情通常会有所不同。

无论如何,我尝试简化代码以解决问题,并成功找到了我的问题。在伪代码中,我最终做了这样的事情:

create pipe1
if ( !fork() ) {
    close pipe1 write fd
   do some stuff reading pipe1 until EOF
}
create pipe2
if ( !fork() )  {
   close pipe2 write fd
   do some stuff reading pipe2 until EOF
}
close pipe1 read fd
close pipe2 read fd
write data to pipe1
get completion response from child 1
close pipe1 write fd
write data to pipe2
get completion response from child 2
close pipe2 write fd
wait for children to exit

读取 pipe1 的子进程挂起,但仅当管道中的数据量变得很大时。即使我关闭了 child1 正在读取的管道,也会发生这种情况。

查看源代码会发现问题。当我派生第二个子进程时,它获取了自己的 pipe1 文件描述符副本,这些文件描述符保持打开状态。即使只有一个进程应该写入管道,但在第二个进程中打开它可以防止它进入 EOF 状态。

小数据集并没有出现问题,因为 child2 正在快速完成其业务并退出。但是对于更大的数据集,child2 并没有快速返回,我最终陷入了僵局。

【问题讨论】:

    标签: linux ipc pipe


    【解决方案1】:

    当作者关闭写端时,read 应该返回 EOF。

    由于您先执行管道然后执行分叉,因此两个进程都将打开写入 fd。可能是在读取过程中您忘记关闭管道的写入部分。

    警告:我已经很久没有在 Unix 上编程了。所以可能不准确。

    这里有一些代码来自:http://www.cs.uml.edu/~fredm/courses/91.308/files/pipes.html。看看下面的“关闭未使用”的 cmets。

    #include <stdio.h>
    
    /* The index of the "read" end of the pipe */
    #define READ 0
    
    /* The index of the "write" end of the pipe */
    #define WRITE 1
    
    char *phrase = "Stuff this in your pipe and smoke it";
    
    main () {
      int fd[2], bytesRead;
    
      char message [100]; /* Parent process message buffer */
    
      pipe ( fd ); /*Create an unnamed pipe*/
    
      if ( fork ( ) == 0 ) {
        /* Child Writer */
        close (fd[READ]); /* Close unused end*/
        write (fd[WRITE], phrase, strlen ( phrase) +1); /* include NULL*/
        close (fd[WRITE]); /* Close used end*/
        printf("Child:  Wrote '%s' to pipe!\n", phrase);
    
      } else {
    
        /* Parent Reader */
        close (fd[WRITE]); /* Close unused end*/ 
        bytesRead = read ( fd[READ], message, 100);
        printf ( "Parent: Read %d bytes from pipe: %s\n", bytesRead, message);
        close ( fd[READ]); /* Close used end */
      } 
    }
    

    【讨论】:

    • 这完全正确。一旦关闭引用管道写入端的 所有 文件描述符,read() 将不会阻塞并返回 0。您可能在某个进程中存在另一个用于写入端的文件描述符(可能在接收进程中,或者可能是另一个子进程在fork() 之后得到了它的副本)。
    • 不,绝对不是第一个问题——在 fork 之后,我有相同的 close() 语句,这些语句出现在这段代码的每个规范实例中。当然,当我的代码仅限于这样的短消息时,我的代码也能正常工作。
    • @Mark:也许显示代码会有所帮助。我猜你不关心已经存在的数据,如果管道关闭?在这种情况下,您可能有两个管道,一个命令和一个数据。让作者通过命令管道发送 CLOSE 命令。您可以使用 select 调用对两个 fd 上的数据进行非阻塞测试。
    • 正如经常发生的那样,简化代码以尝试演示问题导致我的方式错误。我的错误不是太微妙,但它肯定是第一次超越了我。我会在一秒钟内将它添加到我的问题中。
    猜你喜欢
    • 1970-01-01
    • 2012-08-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-02-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多