6

给定以下代码:

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

4 回答 4

18

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

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

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

不; 你是说孩子可以和父母一样写信给管道。任何具有管道写入文件描述符的进程都可以写入管道。

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

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

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

于 2012-07-22T15:28:22.993 回答
3

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

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

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

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

于 2012-07-22T10:31:12.630 回答
1

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

同样,文件表中将有两个条目,其中 1 作为读取端和写入端的引用计数。

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

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

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

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

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

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

read(pipefd[0], buf, sizeof(buf));//buf is a character array sufficiently large  
于 2012-07-22T13:55:23.827 回答
1

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

 A SIGPIPE signal is generated if a write on a pipe with only
 one end is attempted.
于 2012-09-15T20:54:35.863 回答