如果子进程在阅读时不会从写入中关闭管道会发生什么?

时间:2012-07-22 10:22:49

标签: c linux ubuntu fork pipe

给出以下代码:

int main(int argc, char *argv[])
{
    int pipefd[2];
    pid_t cpid;
    char buf;

    if (argc != 2) {
        fprintf(stderr, "Usage: %s \n", argv[0]);
        exit(EXIT_FAILURE);
    }

    if (pipe(pipefd) == -1) {
        perror("pipe");
        exit(EXIT_FAILURE);
    }

    cpid = fork();
    if (cpid == -1) {
        perror("fork");
        exit(EXIT_FAILURE);
    }

    if (cpid == 0) {    /* Child reads from pipe */
        close(pipefd[1]);          /* Close unused write end */

        while (read(pipefd[0], &buf, 1) > 0)
            write(STDOUT_FILENO, &buf, 1);

        write(STDOUT_FILENO, "\n", 1);
        close(pipefd[0]);
        _exit(EXIT_SUCCESS);

    } else {            /* Parent writes argv[1] to pipe */
        close(pipefd[0]);          /* Close unused read end */
        write(pipefd[1], argv[1], strlen(argv[1]));
        close(pipefd[1]);          /* Reader will see EOF */
        wait(NULL);                /* Wait for child */
        exit(EXIT_SUCCESS);
    }
return 0;

}

每当子进程想要从管道读取时,它必须首先关闭管道侧写入。当我从子进程的close(pipefd[1]);中删除该行if时, 我基本上说“好吧,孩子可以从管道中读取,但我允许父母同时写入管道”?

如果是这样,当管道打开以进行读取时会发生什么?写作?没有相互排斥?

4 个答案:

答案 0 :(得分:16)

  

每当子进程想要从管道读取时,它必须首先关闭管道的一面。

如果进程 - 父进程或子进程 - 不打算使用管道的写端,则应该关闭该文件描述符。类似地,对于管道的读取端。系统将假定在任何进程打开写入结束时可能发生写入,即使唯一的此类进程是当前尝试从管道读取的进程,因此系统也不会报告EOF。此外,如果您溢出管道并且仍然存在读取结束打开的进程(即使该进程是尝试写入的进程),则写入将挂起,等待读取器为写入完成留出空间。

  

当我删除该行关闭时(pipefd [1]);从孩子的过程来看,我基本上说“好吧,孩子可以从管道读取,但我允许父母同时写入管道”?

没有;你说孩子可以到管道和父母。具有管道写入文件描述符的任何进程都可以写入管道。

  

如果是这样,当管道打开以进行读写时会发生什么 - 没有相互排斥?

没有任何相互排斥。管道写入描述符打开的任何进程都可以随时写入管道;内核确保两个并发写操作实际上是序列化的。打开管道读取描述符的任何进程都可以随时从管道读取;内核确保两个并发读操作获得不同的数据字节。

通过确保只有一个进程打开以进行写入并且只有一个进程打开以进行读取,确保单向使用管道。但是,这是一个编程决定。你可以有N个进程,写入结束打开,M进程读取结束打开(并且,思想消失,N组和M组进程之间可能存在共同的进程),并且它们都能够出乎意料地工作。但是你不可能很容易地预测在写入数据包后会在哪里读取数据。

答案 1 :(得分:3)

fork()复制文件句柄,因此管道的每一端都有两个句柄。

现在,考虑一下。如果父级未关闭管道的未使用端,则仍会有两个句柄。如果孩子死了,孩子一侧的把手就会消失,但父母仍然保持打开的把手 - 因此,管子仍然完全无效,所以永远不会有“破管”或“EOF”到达。没有人再把数据放进去了。

当然,另一个方向相同。

是的,父母/孩子仍然可以使用句柄写入自己的管道;我不记得这个用例,它仍然会给你同步问题。

答案 2 :(得分:1)

创建管道时,它的两端是读端和写端。这些是用户文件描述符表中的条目。

类似地,File表中将有两个条目,其中1作为读取结束和写入结束的引用计数。

现在,在fork时,会创建一个子文件,文件描述符是重复的,因此文件表中两端的引用计数变为2。

现在“当我删除该行关闭(pipefd [1])” - >在这种情况下,即使父级已完成写入,此行下方的while循环也将阻止读取返回0(即EOF)。发生这种情况,因为即使父进程已完成写入并关闭管道的写入结束,File表中写入结尾的引用计数仍为1(最初为2),因此读取函数仍在等待某些数据到达哪个永远不会发生。

现在如果你还没写“close(pipefd [0]);”在父级中,此当前代码可能不会显示任何问题,因为您在父级中编写了一次。

但是如果你写了不止一次,那么理想情况下你会想要得到一个错误(如果孩子不再读),但由于父母的读取结束没有关闭,你将不会得到错误(即使孩子不在那里阅读)。

因此,当我们不断读取/写入数据时,不关闭未使用端的问题变得明显。如果我们只是读取/写入一次数据,这可能并不明显。

就像代替孩子中的读取循环一样,你只使用下面的一行,你可以一次性获取所有数据,而不关心检查EOF,你的程序即使你是不写“close(pipefd [1]);”在孩子身上。

read(pipefd[0], buf, sizeof(buf));//buf is a character array sufficiently large  

答案 3 :(得分:1)

SunOS管道()的手册页: -      仅使用一个空管道(无缓冲数据)读取调用      结束(所有写文件描述符都关闭)返回一个EOF(结束      的文件)。

 A SIGPIPE signal is generated if a write on a pipe with only
 one end is attempted.
相关问题