当子进程退出时,父应该如何关闭管道文件描述符

时间:2014-05-16 13:34:17

标签: c unix fork posix pipe

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

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

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

通常,当子进程退出时,父进程应该使用管道文件描述符做什么?

4 个答案:

答案 0 :(得分:3)

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

答案 1 :(得分:3)

第一遍:在明确表示存在使用select()的循环并且孩子们发送了多封邮件之前。

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

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

从你的问题来看,儿童过程中是否存在来自儿童过程的单一信息,或者当孩子正在工作时是否存在“意识流”统计报告,这一点并不完全清楚。如果有一条消息(小于管道缓冲区大小),那么生活很简单。如果有消息流或消息长度超过管道缓冲区大小,则必须更仔细地考虑协调 - 只有当孩子死亡时才能检测到消息。

第二遍:获得额外信息后。

如果您已经在循环中使用select(),那么当一个孩子死亡时,您将从select()获得“准备好读取”指示,并且您将从{{获得0个字节1}}表示该管道上的EOF。然后你应该关闭那个管道(并等待一个或多个waitpid()的孩子,可能使用read() - 应该至少有一个尸体被收集 - 所以你没有僵尸踢旷日持久)。

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

答案 2 :(得分:0)

当您写入管道但没有打开fd从该管道读取时,会发生

broken pipe。所以它不适用于您的情况。在您的情况下,由于您的父级正在从管道读取,因此当子级退出时它应该读取EOF(如果您已正确关闭父进程中的写入结束,否则它将阻止,因为它假定仍然存在将来要阅读的内容)。然后,您可以安全地关闭父进程中的读取fd。

一般

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

答案 3 :(得分:0)

让我们看看父母有 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 被更新,父进程知道管道的另一端没有连接。