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

时间:2022-11-05 22:45:47

Given the following code:

给出以下代码:

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;

}

Whenever the child process wants to read from the pipe, it must first close the pipe's side from writing. When I remove that line close(pipefd[1]); from the child process's if, I'm basically saying that "okay, the child can read from the pipe, but I'm allowing the parent to write to the pipe at the same time"?

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

If so, what would happen when the pipe is open for both reading & writing? No mutual exclusion?

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

4 个解决方案

#1


15  

Whenever the child process wants to read from the pipe, it must first close the pipe's side from writing.

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

If the process — parent or child — is not going to use the write end of a pipe, it should close that file descriptor. Similarly for the read end of a pipe. The system will assume that a write could occur while any process has the write end open, even if the only such process is the one that is currently trying to read from the pipe, and the system will not report EOF, therefore. Further, if you overfill a pipe and there is still a process with the read end open (even if that process is the one trying to write), then the write will hang, waiting for the reader to make space for the write to complete.

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

When I remove that line close(pipefd[1]); from the child's process IF, I'm basically saying that "okay, the child can read from the pipe, but I'm allowing the parent to write to the pipe at the same time"?

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

No; you're saying that the child can write to the pipe as well as the parent. Any process with the write file descriptor for the pipe can write to the pipe.

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

If so, what would happen when the pipe is open for both reading and writing — no mutual exclusion?

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

There isn't any mutual exclusion ever. Any process with the pipe write descriptor open can write to the pipe at any time; the kernel ensures that two concurrent write operations are in fact serialized. Any process with the pipe read descriptor open can read from the pipe at any time; the kernel ensures that two concurrent read operations get different data bytes.

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

You make sure a pipe is used unidirectionally by ensuring that only one process has it open for writing and only one process has it open for reading. However, that is a programming decision. You could have N processes with the write end open and M processes with the read end open (and, perish the thought, there could be processes in common between the set of N and set of M processes), and they'd all be able to work surprisingly sanely. But you'd not readily be able to predict where a packet of data would be read after it was written.

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

#2


3  

fork() duplicates the file handles, so you will have two handles for each end of the pipe.

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

Now, consider this. If the parent doesn't close the unused end of the pipe, there will still be two handles for it. If the child dies, the handle on the child side goes away, but there's still the open handle held by the parent -- thus, there will never be a "broken pipe" or "EOF" arriving because the pipe is still perfectly valid. There's just nobody putting data into it anymore.

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

Same for the other direction, of course.

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

Yes, the parent/child could still use the handle to write into their own pipe; I don't remember a use-case for this, though, and it still gives you synchronization problems.

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

#3


1  

When the pipe is created it is having two ends the read end and write end. These are entries in the User File descriptor table.

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

Similarly there will be two entries in the File table with 1 as reference count for both the read end and the write end.

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

Now when you fork, a child is created that is the file descriptors are duplicated and thus the reference count of both the ends in the file table becomes 2.

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

Now "When I remove that line close(pipefd[1])" -> In this case even if the parent has completed writing, your while loop below this line will block for ever for the read to return 0(ie EOF). This happens since even if the parent has completed writing and closed the write end of the pipe, the reference count of the write end in the File table is still 1 (Initially it was 2) and so the read function still is waiting for some data to arrive which will never happen.

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

Now if you have not written "close(pipefd[0]);" in the parent, this current code may not show any problem, since you are writing once in the parent.

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

But if you write more than once then ideally you would have wanted to get an error (if the child is no longer reading),but since the read end in the parent is not closed, you will not be getting the error (Even if the child is no more there to read).

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

So the problem of not closing the unused ends become evident when we are continuously reading/writing data. This may not be evident if we are just reading/writing data once.

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

Like if instead of the read loop in the child, you are using only once the line below, where you are getting all the data in one go, and not caring to check for EOF, your program will work even if you are not writing "close(pipefd[1]);" in the child.

就像如果不是在孩子读圈,您只使用一次,下面的线,你在哪里得到的所有数据一气呵成,并没有照顾到检查EOF,你的程序将工作,即使你不写“关闭(pipefd [1]);”在孩子身上。

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

#4


1  

man page for pipe() for SunOS :- Read calls on an empty pipe (no buffered data) with only one end (all write file descriptors closed) return an EOF (end of file).

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

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

#1


15  

Whenever the child process wants to read from the pipe, it must first close the pipe's side from writing.

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

If the process — parent or child — is not going to use the write end of a pipe, it should close that file descriptor. Similarly for the read end of a pipe. The system will assume that a write could occur while any process has the write end open, even if the only such process is the one that is currently trying to read from the pipe, and the system will not report EOF, therefore. Further, if you overfill a pipe and there is still a process with the read end open (even if that process is the one trying to write), then the write will hang, waiting for the reader to make space for the write to complete.

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

When I remove that line close(pipefd[1]); from the child's process IF, I'm basically saying that "okay, the child can read from the pipe, but I'm allowing the parent to write to the pipe at the same time"?

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

No; you're saying that the child can write to the pipe as well as the parent. Any process with the write file descriptor for the pipe can write to the pipe.

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

If so, what would happen when the pipe is open for both reading and writing — no mutual exclusion?

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

There isn't any mutual exclusion ever. Any process with the pipe write descriptor open can write to the pipe at any time; the kernel ensures that two concurrent write operations are in fact serialized. Any process with the pipe read descriptor open can read from the pipe at any time; the kernel ensures that two concurrent read operations get different data bytes.

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

You make sure a pipe is used unidirectionally by ensuring that only one process has it open for writing and only one process has it open for reading. However, that is a programming decision. You could have N processes with the write end open and M processes with the read end open (and, perish the thought, there could be processes in common between the set of N and set of M processes), and they'd all be able to work surprisingly sanely. But you'd not readily be able to predict where a packet of data would be read after it was written.

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

#2


3  

fork() duplicates the file handles, so you will have two handles for each end of the pipe.

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

Now, consider this. If the parent doesn't close the unused end of the pipe, there will still be two handles for it. If the child dies, the handle on the child side goes away, but there's still the open handle held by the parent -- thus, there will never be a "broken pipe" or "EOF" arriving because the pipe is still perfectly valid. There's just nobody putting data into it anymore.

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

Same for the other direction, of course.

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

Yes, the parent/child could still use the handle to write into their own pipe; I don't remember a use-case for this, though, and it still gives you synchronization problems.

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

#3


1  

When the pipe is created it is having two ends the read end and write end. These are entries in the User File descriptor table.

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

Similarly there will be two entries in the File table with 1 as reference count for both the read end and the write end.

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

Now when you fork, a child is created that is the file descriptors are duplicated and thus the reference count of both the ends in the file table becomes 2.

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

Now "When I remove that line close(pipefd[1])" -> In this case even if the parent has completed writing, your while loop below this line will block for ever for the read to return 0(ie EOF). This happens since even if the parent has completed writing and closed the write end of the pipe, the reference count of the write end in the File table is still 1 (Initially it was 2) and so the read function still is waiting for some data to arrive which will never happen.

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

Now if you have not written "close(pipefd[0]);" in the parent, this current code may not show any problem, since you are writing once in the parent.

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

But if you write more than once then ideally you would have wanted to get an error (if the child is no longer reading),but since the read end in the parent is not closed, you will not be getting the error (Even if the child is no more there to read).

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

So the problem of not closing the unused ends become evident when we are continuously reading/writing data. This may not be evident if we are just reading/writing data once.

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

Like if instead of the read loop in the child, you are using only once the line below, where you are getting all the data in one go, and not caring to check for EOF, your program will work even if you are not writing "close(pipefd[1]);" in the child.

就像如果不是在孩子读圈,您只使用一次,下面的线,你在哪里得到的所有数据一气呵成,并没有照顾到检查EOF,你的程序将工作,即使你不写“关闭(pipefd [1]);”在孩子身上。

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

#4


1  

man page for pipe() for SunOS :- Read calls on an empty pipe (no buffered data) with only one end (all write file descriptors closed) return an EOF (end of file).

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

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