在 C 中正确使用 close

The proper use of close in C

我对如何在 C 中正确使用 close 到 close 管道感到困惑。我对 C 还很陌生,所以如果这太基础了我深表歉意,但我在其他地方找不到任何解释。

#include <stdio.h>

int main()
{
    int fd[2];
    pipe(fd);
    if(fork() == 0) {
        close(0);
        dup(fd[0]);
        close(fd[0]);
        close(fd[1]);
    } else {
        close(fd[0]);
        write(fd[1], "hi", 2);
        close(fd[1]);
    }
    wait((int *) 0);
    exit(0);

}

我的第一个问题是:在上面的代码中,child进程会关闭fd的写端。如果我们先到达close(fd[1]),然后parent进程到达write(fd[1], "hi", 2),那fd[1]不就已经关闭了吗?

int main()
{
    char *receive;
    int[] fd;
    pipe(fd);
    if(fork() == 0) {
       while(read(fd[0], receive, 2) != 0){
            printf("got u!\n");
       }
    } else {
        for(int i = 0; i < 2; i++){
            write(fd[1], 'hi', 2);
        }
        close(fd[1]);
    }
    wait((int *) 0);
    exit(0);

}

第二个问题是:在上面的代码中,我们是否可以在child进程接收完所有内容之前,在parent进程中到达close(fd[1])?如果是,那么 parent 和 child 之间的正确通信方式是什么?这里我的理解是,如果我们在parent中不关闭fd[1],那么read会一直阻塞,程序也不会退出。

what is the correct way to communicate between parent and child[?]

parent 在分叉之前创建管道。在 fork 之后,parent 和 child 各自关闭它们未使用的管道端(管道应被视为单向;如果需要双向通信,请创建两个)。每个进程都有自己的每个 pipe-end 文件描述符副本,因此这些关闭不会影响其他进程使用管道的能力。然后,每个进程都使用它保持开放的一端来实现其方向性——写入写入端或从读取端读取。

当编写器完成将它打算写入管道的所有内容写入管道时,它就会关闭它的末端。这很重要,有时甚至是必不可少的,因为只要任何进程打开了写入端,reader 就不会在管道的读取端感知到 end-of-file。这也是为什么每个进程关闭它未使用的一端很重要的原因之一,因为如果 reader 也打开了写入端,那么它可以无限期地阻止尝试从管道读取,无论是什么任何其他进程都可以。

当然,reader 也应该在读取结束时关闭它(或终止,让系统处理)。不这样做就构成了过度的资源消耗,但这是否是一个严重的问题要视情况而定。

首先请注意,在 fork() 之后,文件描述符 fd 也会被复制到 child 进程。所以基本上,pipe 就像一个文件,每个进程都有自己对 readwrite 末尾的引用 pipe。本质上有 2 个读取和 2 个写入文件描述符,每个进程一个。

My first question is: In the above code, the child process will close the write side of fd. If we first reach close(fd[1]), then the parent process reach write(fd[1], "hi", 2), wouldn't fd[1] already been closed?

答:不是。parent进程中的fd[1]是parent的写端。 child 通过关闭 its fd[1] 放弃了在管道上写入的权利,这不会阻止 parent 写入管道。

在回答第二个问题之前,我将您的代码修改为 运行 并产生了一些结果。

int main()
{
    char receive[10];
    int fd[2];
    pipe(fd);
    if(fork() == 0) {
        close(fd[1]);      <-- Close UNUSED write end
       while(read(fd[0], receive, 2) != 0){
            printf("got u!\n");
            receive[2] = '[=10=]';
            printf("%s\n", receive);
       }
       close(fd[0]);       <-- Close read end after reading
    } else {
        close(fd[0]);      <-- Close UNUSED read end
        for(int i = 0; i < 2; i++){
            write(fd[1], "hi", 2);
        }
        close(fd[1]);      <-- Close write end after writing

        wait((int *) 0);
    }
    exit(0);

}

结果:

got u!
hi
got u!
hi

注意: 我们(貌似)丢失了一个 hi 因为我们正在将它读入同一个数组 receive,这实际上覆盖了第一个 hi.您可以使用二维字符数组来保留这两条消息。

The second question is: In the above code, would it be possible for us to reach close(fd[1]) in the parent process before the child process finish receiving all the contents?

答:是的。写入 pipe() 是 non-blocking(除非另有说明),直到管道缓冲区已满。

If yes, then what is the correct way to communicate between parent and child. My understanding here is that if we do not close fd[1] in the parent, then read will keep being blocked, and the program won't exit either.

如果我们在parent中关闭fd[1],会提示parent已经关闭了它的写端。然而,如果 child 没有提前关闭它的 fd[1],它会在 read() 上阻塞,因为管道不会发送 EOF,直到所有写端都关闭。因此 child 将等待自己写入 pipe,同时从中读取!

现在,如果 parent 不关闭其未使用的读取端会发生什么?如果文件只有一个读取描述符(比如带有 child 的那个),那么一旦 child 关闭它,parent 将在尝试进一步写入时收到一些信号或错误pipe 因为没有读者。

但是在这种情况下,parent 也有一个打开的读描述符,它将能够写入缓冲区直到它被填满,这可能会导致下一个 write 调用出现问题,如果有的话。

现在这可能没有多大意义,但是如果您编写的程序需要一次又一次地通过管道传递值,那么不关闭未使用的末端会经常让您遇到令人沮丧的错误。