【问题标题】:How should parent close pipe file descriptor when child process exits子进程退出时父级应如何关闭管道文件描述符
【发布时间】:2014-07-04 23:27:36
【问题描述】:

我正在创建一个 TCP 服务,该服务在每次客户端连接时分叉一个新进程。在分叉之前,我设置了一个管道,以便孩子可以将连接期间收集的统计信息发送回父级。父关闭写端,子关闭读端,父维护一个读端文件描述符数组,每个子一个。

当孩子完成连接并退出时,我不确定如何处理这些文件描述符。孩子是否需要通过管道通知父母它即将退出,以便父母可以关闭管道?或者父进程可以在子进程退出后自动检测到损坏的管道并关闭它?

父程序中的代码正在运行一个循环,其中select() 检测侦听套接字和子管道的读取端上的活动。每个子进程在运行时可以向父进程发送多条消息。

一般来说,当子进程退出时,父进程应该如何处理管道文件描述符?

【问题讨论】:

    标签: c unix fork posix pipe


    【解决方案1】:

    第一遍:在明确使用select() 存在循环并且孩子发送了多条消息之前。

    如果父进程维护一个文件描述符数组,它还需要将每个文件描述符与一个子进程相关联。如果孩子们在死前发送了一条小的统计信息,那么当主程序等待死去的孩子时,它知道哪个孩子死了,所以它可以关闭它刚刚发现死的孩子的文件描述符(在确保通过进行一次或多次最终读取,管道为空)。

    另一种机制使用select()poll() 或相关函数来报告对文件描述符的读取操作何时不会挂起。当它从管道中检测到 EOF(读取零字节)时,它就知道孩子死了。但是,这可能更难处理。

    您的问题并不完全清楚,在子进程退出时是否有一条消息,或者在子进程工作时是否有“意识流”统计报告。如果只有一条消息(小于管道缓冲区大小),那么生活很容易。如果有消息流或消息长于管道缓冲区大小,则必须更仔细地考虑协调问题——不能仅在子节点死亡时检测消息。

    第二遍:在额外信息可用之后。

    如果您已经在循环中使用select(),那么当一个孩子死亡时,您将从select() 获得“管道准备读取”指示,并且您将从read() 获得0 个字节,表示EOF在那根管子上。然后,您应该关闭该管道(并等待一个或多个带有waitpid() 的孩子,可能使用W_NOHANG - 应该至少收集一具尸体 - 这样您就不会长时间让僵尸四处奔波)。

    对您最后一个问题的严格回答是:当具有管道写入端的唯一子级死亡时,父级应关闭该管道的读取端以释放资源以供以后重用。

    【讨论】:

    • 有一个消息流,所以我需要在父级中使用select 循环来在所有管道和监听套接字之间进行选择。如果 select 告诉我管道已准备好读取,并且 read 返回 0 (EOF),我应该能够关闭父端的管道,不是吗?
    • 是的;如果您已经在使用select(),那么您将获得“管道准备读取”,然后是 EOF(读取 0 字节),然后您可以关闭该管道(并等待一个孩子——应该有一具尸体收集起来,所以你不会让僵尸长时间乱踢)。
    • 对您最后一个问题的严格回答是:当唯一具有管道写入端的子节点死亡时,父节点应关闭该管道的读取端以释放资源以供以后重用。
    • 请注意,您不必清理尸体拾取代码中的 fd。 select 将返回“读取不会阻塞此 fd”,此时您将对其进行清理。
    • @tmyklebu:既然评论信息表明每个孩子都有一条消息流,而不是最后一个统计摘要,那么(如我之前的“是的;如果你已经在使用select()..." 注释指出),您不需要清理 waitpid() 代码中的文件描述符。我最初的想法是程序会监听套接字,并会定期运行waitpid()W_NOHANG 来收集孩子——省略了各种细节——并在那时读取单个消息。随着问题的变化,答案和 cmets 也会发生变化。
    【解决方案2】:

    在您的情况下,父进程应在 fork 之后立即关闭管道的写入端。然后它可以读取它的统计数据直到EOF(文件结束),然后关闭管道的读取端。

    【讨论】:

    • 我已经关闭了父母的写作端和孩子的阅读端。你是说当孩子退出时父母read会报告EOF,我可以这样检测吗?
    • 是的;如果孩子死了,管道上的read() 将返回 0,表示 EOF。如果管道的写端是打开的,读会挂起,直到有数据要读或者管道的写端关闭。
    • @Andrew:考虑文件描述符的方法是作为指向文件的引用计数指针。 dup2 复制一个指针,close 将其无效。一旦写入的文件消失(因为它不再被引用),从读取端读取将返回EOF。
    【解决方案3】:

    broken pipe 在您写入管道但没有打开 fd 以从该管道读取时发生。所以它不适用于你的情况。在您的情况下,由于您的父级正在从管道中读取,因此它应该在子级退出时读取EOF(如果您已正确关闭父进程中的写入端,否则它将只是阻塞,因为它假定仍有事情要处理以后再读)。然后你就可以安全的关闭你父进程中的read fd了。

    一般情况

    如果父级写入而子级读取,您确实需要担心broken pipe,即当子级关闭读取 fd 时,父级会在继续写入管道时获得 SIGPIPE。 SIGPIPE 默认终止进程,因此您可能需要设置一个信号处理程序以使其执行您想要的任何操作(如果您不希望它只是终止)。

    【讨论】:

      【解决方案4】:

      让我们看看父母有 1 个孩子和孩子的不同情况。

      • 父母有 1 个孩子。当子进程退出并且父进程正在等待读取结束时,父进程也将退出。这是代码
      //fd[0]     //read end
      //fd[1]     //write end
      
      #include <unistd.h>
      #include <stdio.h>
      #include <errno.h>              //For errno
      #include <stdlib.h>              //exit()
      
      void DumpAndExit(char* str){
          perror (str);
          printf ("errno: %d", errono);
          exit(0);  
      }
      
      int main(){
        int fd[2], pid = -1;
      
        if (pipe(fd) < 0)
          DumpAndExit ("pipe");
      
        if (pid = fork() < 0) {
          DumpAndExit ("fork");
        }
        else if (pid == 0) {                              //Child
          close(fd[0]);                                   //Close read end
          printf("In Child \n");
          sleep(2);
          exit(0);
        } else {                                         //Parent
          close(fd[1]);                                  //close write
          waitpid(-1);                                   //Parent will wait for child
          printf("Parent waiting\n");
          char buf[4] = {};
          read(fd[0], buf, sizeof(buf));                //reads from pipe
          printf ("Read from child: %s", buf);
        }
      }
      
      # ./a.out
      In child 
      Parent waiting
      Read from child:
      #
      

      简单来说:

      • 每个进程都有一个PCB(struct task_struct),其中包含进程的所有信息,在 fork() 的情况下,它也将具有子上下文。表示指向子 PCB 的指针。
      • 由于管道即int fd[2] 是在父堆栈上创建的,然后复制到子堆栈。当子进程退出时,它的 PCB 被清除,父进程的 PCB 被更新并且父进程知道管道的另一端没有连接。

      【讨论】:

        猜你喜欢
        • 2012-04-29
        • 1970-01-01
        • 2021-03-12
        • 2021-05-23
        • 1970-01-01
        • 1970-01-01
        • 2017-06-20
        • 2014-03-28
        • 2018-11-29
        相关资源
        最近更新 更多