【问题标题】:What happens if a child process won't close the pipe from writing, while reading?如果子进程在读取时不关闭管道写入,会发生什么?
【发布时间】:2012-07-20 22:01:04
【问题描述】:

给定以下代码:

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;

}

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

如果是这样,当管道为读写打开时会发生什么?没有互斥?

【问题讨论】:

  • Read() 和 write() 到管道保证是原子的。 (最大 PIPE_BUFF 大小)。这意味着:先到先得。写/读部分将交错,但它们的边界仍然完好无损。
  • 如果你不关闭孩子的管道写端,孩子将永远不会看到 EOF,因为只有在所有用户都关闭了写端时才会出现 EOF。有关示例,请参见 stackoverflow.com/questions/7868018/…

标签: c linux ubuntu fork pipe


【解决方案1】:

每当子进程想要从管道中读取数据时,它必须首先关闭管道的一侧以防止写入。

如果进程(父进程或子进程)不打算使用管道的写入端,它应该关闭该文件描述符。对于管道的读取端也是如此。系统将假定在任何进程的写入端打开时都可能发生写入,即使唯一的此类进程是当前正在尝试从管道读取的进程,因此系统不会报告 EOF。此外,如果您过度填充管道并且仍然有一个读取端打开的进程(即使该进程是试图写入的进程),那么写入将挂起,等待读者为写入腾出空间来完成。

当我删除该行时 close(pipefd[1]);从子进程 IF 来看,我基本上是在说“好吧,子进程可以从管道读取,但我允许父进程同时写入管道”?

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

如果是这样,当管道对读写都打开时会发生什么——没有互斥?

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

通过确保只有一个进程将其打开以供写入并且只有一个进程将其打开以供读取,从而确保单向使用管道。然而,这是一个编程决定。您可以有 N 个写入端打开的进程和 M 个读取端打开的进程(并且,别想了,N 组和 M 组进程之间可能存在共同的进程),它们都能够出奇地理智地工作。但是你很难预测数据包在写入后会被读取到哪里。

【讨论】:

  • 很好的答案!如果可以的话 +100!
【解决方案2】:

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

现在,考虑一下。如果父级没有关闭未使用的管道端,它仍然会有两个句柄。如果子进程死了,子进程的句柄就会消失,但父进程仍然持有打开的句柄——因此,永远不会有“断管”或“EOF”到达,因为管道仍然完全有效。只是没有人再将数据放入其中。

当然,另一个方向也是如此。

是的,父/子仍然可以使用句柄写入自己的管道;不过,我不记得有什么用例,它仍然会给您带来同步问题。

【讨论】:

    【解决方案3】:

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

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

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

    现在“当我删除该行时 close(pipefd[1])”-> 在这种情况下,即使父级已完成写入,此行下方的 while 循环将永远阻塞,以便读取返回 0(即 EOF )。发生这种情况是因为即使 parent 完成了写入并关闭了管道的写入端,File 表中写入端的引用计数仍然为 1(最初为 2),因此读取函数仍在等待一些数据到达,这永远不会发生。

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

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

    所以当我们连续读/写数据时,不关闭未使用端的问题就变得很明显了。如果我们只是读/写一次数据,这可能并不明显。

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

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

    【讨论】:

    • 注意open file descriptionsopen file descriptors之间的区别。准确地说真的很难。计数在打开的文件描述中,而不是在描述符中。
    • @JonathanLeffler 谢谢乔纳森。我在答案中提到的“文件表条目”和“打开文件描述”希望它们指的是同一件事。
    【解决方案4】:

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2010-11-01
      • 1970-01-01
      • 2018-04-07
      • 1970-01-01
      • 1970-01-01
      • 2014-01-20
      • 1970-01-01
      相关资源
      最近更新 更多