【问题标题】:The proper use of close in CC语言中close的正确使用
【发布时间】:2020-11-16 23:54:24
【问题描述】:

我对如何在 C 中正确使用接近关闭的管道感到困惑。我对 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);

}

我的第一个问题是:在上面的代码中,子进程会关闭fd的写端。如果我们先到达close(fd[1]),然后父进程到达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);

}

第二个问题是:在上面的代码中,我们有没有可能在子进程接收完所有内容之前,到达父进程中的close(fd[1])?如果是,那么父母与孩子之间的正确沟通方式是什么。我的理解是,如果我们不关闭父级中的fd[1],那么读取会一直被阻塞,程序也不会退出。

【问题讨论】:

  • 一个文件直到所有引用它的进程都关闭它们的描述符后才真正关闭。所以关闭发生的顺序无关紧要,因为其他进程保持打开状态。
  • 子进程可以关闭其文件描述符,但这不会将父进程的文件描述符与内核中的底层打开文件描述分离。 反之亦然
  • 您的子进程不会从管道中读取,但可以。您的父进程确实会写入子进程。在所有正常情况下,关闭都是正确的。在异常情况下,使用dup2() 可能更安全,但我们说的是非常异常的情况。
  • 在您的第二个程序中,您有一个未初始化的指针。读进去会让人不开心。

标签: c pipe system-calls


【解决方案1】:

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

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

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

在回答第二个问题之前,我修复了您的代码以实际运行它并产生一些结果。

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] = '\0';
            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。您可以使用 2D 字符数组来保留这两条消息。

第二个问题是:在上面的代码中,我们是否可以 在子进程之前到达父进程中的 close(fd[1]) 接收完所有内容?

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

如果是,那么什么是正确的 父母和孩子之间的沟通方式。我的理解是 如果我们不关闭父级中的 fd[1],那么 read 将继续存在 被阻塞,程序也不会退出。

如果我们关闭父级中的fd[1],它将表明父级已关闭其写入端。但是,如果子进程之前没有关闭其fd[1],它将阻塞read(),因为管道不会发送EOF,直到所有写端都关闭。所以孩子会期待自己写到pipe,同时读取它!

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

但是在这种情况下,父级还打开了一个读取描述符,它可以写入缓冲区直到它被填满,这可能会导致下一次write 调用出现问题(如果有)。

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

【讨论】:

  • 非常感谢,我学到了很多!但是考虑到你新版本的代码,如果父进程先关闭 fd[1] ,是不是子进程无法读取所有正在发送的内容?我想我的问题是:“读取”会在两个管道关闭后立即停止读取,还是会在管道关闭之前继续读取管道中已经存在的所有内容?
  • 在读取器的缓冲区已满(从管道读取所有内容)或收到EOF 之前,从管道读取不会停止。关闭其写入端的父级不会阻止子级读取管道中已经存在的任何内容。然而,父母没有关闭其写入端,将在read 调用中阻止孩子,因为它不会收到EOF 并继续期待管道中的内容。
  • 另请注意,EOF 仅在其所有可用的写入端都关闭时才通过管道发送给读者。所以这也是为什么你应该先关闭 child 中未使用的写端的原因。如有任何进一步的说明,请随时提出。
【解决方案2】:

父母和孩子之间的正确沟通方式是什么[?]

父级在分叉之前创建管道。在分叉之后,父子节点各自关闭他们不使用的管道端(管道应该被认为是单向的;如果你想要双向通信,请创建两个)。每个进程都有自己的每个管道端文件描述符的副本,因此这些闭包不会影响其他进程使用管道的能力。然后,每个进程使用它保持打开的一端来满足其方向性 - 写入写入端或从读取端读取。

当作者写完它打算写入管道的所有内容时,它会关闭它的结尾。这很重要,有时甚至是必不可少的,因为只要任何进程的写入端打开,读取器就不会在管道的读取端感知文件结束。这也是为什么每个进程关闭它不使用的端很重要的一个原因,因为如果读取器还打开了写端,那么它可以无限期地阻止尝试从管道读取,而不管任何其他进程做什么.

当然,阅读器完成后也应该关闭阅读端(或终止,让系统处理)。不这样做就构成了过度的资源消耗,但这是否是一个严重的问题取决于具体情况。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-10-06
    • 2016-01-01
    • 1970-01-01
    • 2012-12-06
    • 1970-01-01
    • 2013-01-23
    相关资源
    最近更新 更多