2010-03-04 118 views
16

如何在不让父进程等待子进程死亡的情况下追踪子进程的死亡?追踪子进程的死亡

我正在尝试一个客户端 - 服务器场景,其中服务器接受来自客户端的连接,并为其接受的每个连接分配一个新进程。

我忽略了SIGCHLD信号来防止僵尸创建。

signal(SIGCHLD, SIG_IGN); 
while(1) 
{ 
    accept(); 
    clients++; 
    if(fork() ==0) 
    { 
    childfunction(); 
    clients--; 
    } 
    else 
    { 
    } 
} 

在上述情况下的问题是,如果子进程得到的childfunction()功能被杀,全局变量clients是没有得到递减。

注:我找不使用SIGCHLD信号的解决方案......如果可能的话

+0

你可以在SIGCHLD – tristan 2010-03-04 08:40:49

+0

的信号处理程序中做些什么我已经提到过... SIGCHLD信号被忽略..? – codingfreak 2010-03-04 08:45:45

+7

+1的疯狂戏剧题目和开头句子。 D: – 2010-03-04 09:22:48

回答

22

通常你写SIGCHLD处理程序,其对PID -1呼吁waitpid()。你可以使用它的返回值来确定哪个pid死掉了。例如:

void my_sigchld_handler(int sig) 
{ 
    pid_t p; 
    int status; 

    while ((p=waitpid(-1, &status, WNOHANG)) != -1) 
    { 
     /* Handle the death of pid p */ 
    } 
} 

/* It's better to use sigaction() over signal(). You won't run into the 
* issue where BSD signal() acts one way and Linux or SysV acts another. */ 

struct sigaction sa; 

memset(&sa, 0, sizeof(sa)); 
sa.sa_handler = my_sigchld_handler; 

sigaction(SIGCHLD, &sa, NULL); 

另外,您可以拨打waitpid(pid, &status, 0)与指定的子进程ID,并同步等待它死了。或者使用WNOHANG检查其状态而不阻塞。

+0

但在我的例子中,SIGCHLD是IGNORED? – codingfreak 2010-03-04 09:54:27

+1

@codingfreak我建议你可能想重新评估一下。你不需要忽略它来避免僵尸。当你waitpid()时,“僵尸”会消失。 – asveikau 2010-03-04 09:58:16

+0

@asveikau - 我想创建一个守护进程..并使用SIGCHLD处理程序破坏了应用程序的稳定性.. 所有的时间父进程应该在SIGCHLD处理程序中等待下来.. – codingfreak 2010-03-04 10:06:49

2

你不想要一个僵尸。如果子进程死亡,并且父进程仍处于运行状态,但从不发出调用来获取状态,则系统不会释放与子进程相关的资源,并且在进程表中留下僵尸/停止进程。

试着改变你的SIGCHLD处理程序更接近于以下内容:


void chld_handler(int sig) { 
    pid_t p; 
    int status; 

    /* loop as long as there are children to process */ 
    while (1) { 

     /* retrieve child process ID (if any) */ 
     p = waitpid(-1, &status, WNOHANG); 

     /* check for conditions causing the loop to terminate */ 
     if (p == -1) { 
      /* continue on interruption (EINTR) */ 
      if (errno == EINTR) { 
       continue; 
      } 
      /* break on anything else (EINVAL or ECHILD according to manpage) */ 
      break; 
     } 
     else if (p == 0) { 
      /* no more children to process, so break */ 
      break; 
     } 

     /* valid child process ID retrieved, process accordingly */ 
     ... 
    } 
} 

使用sigprocmask()信号处理程序的执行过程中,您可以选择性地屏蔽/块额外SIGCHLD信号。当信号处理程序完成时,屏蔽掩码必须返回到其原始值。

如果您确实不想使用SIGCHLD处理程序,则可以尝试将子处理循环添加到定期调用的某个位置并轮询已终止的子项。

+0

按照您所说的进行更改后..我没有看到任何创建的僵尸..让我看看它如何在HTTP服务器守护进程中运行...... – codingfreak 2010-03-05 08:49:21

+0

我不认为在守护进程中使用此代码会有任何主要问题。 – jschmier 2010-03-08 16:30:39

0

变量'clients'在fork()之后位于不同的进程地址空间中,当您在子级中减小变量时,这不会影响父级中的值。我认为你需要处理SIGCHLD来正确处理计数。

4

到目前为止,没有任何解决方案提供了一种不使用SIGCHLD作为问题请求的方法。这是因为在this answer概述使用poll另一种方法的实现(这也解释了为什么你应该避免使用在这样的情况下SIGCHLD):

确保您从您创建的每个子进程有一个管/ 。它可以是他们的stdin/stdout/stderr或者只是一个额外的虚拟fd。当子进程终止时,管道的末端将被关闭,并且主事件循环将检测该文件描述符上的活动。从它关闭的事实中,你认识到子进程已经死亡,并且调用waitpid来获得僵尸。

(注意:我省略了一些最佳实践,如错误检查和清理为了简洁文件描述符)

/** 
* Specifies the maximum number of clients to keep track of. 
*/ 
#define MAX_CLIENT_COUNT 1000 

/** 
* Tracks clients by storing their process IDs and pipe file descriptors. 
*/ 
struct process_table { 
    pid_t clientpids[MAX_CLIENT_COUNT]; 
    struct pollfd clientfds[MAX_CLIENT_COUNT]; 
} PT; 

/** 
* Initializes the process table. -1 means the entry in the table is available. 
*/ 
void initialize_table() { 
    for (int i = 0; i < MAX_CLIENT_COUNT; i++) { 
     PT.clientfds[i].fd = -1; 
    } 
} 

/** 
* Returns the index of the next available entry in the process table. 
*/ 
int get_next_available_entry() { 
    for (int i = 0; i < MAX_CLIENT_COUNT; i++) { 
     if (PT.clientfds[i].fd == -1) { 
      return i; 
     } 
    } 
    return -1; 
} 

/** 
* Adds information about a new client to the process table. 
*/ 
void add_process_to_table(int i, pid_t pid, int fd) { 
    PT.clientpids[i] = pid; 
    PT.clientfds[i].fd = fd; 
} 

/** 
* Removes information about a client from the process table. 
*/ 
void remove_process_from_table(int i) { 
    PT.clientfds[i].fd = -1; 
} 

/** 
* Cleans up any dead child processes from the process table. 
*/ 
void reap_zombie_processes() { 
    int p = poll(PT.clientfds, MAX_CLIENT_COUNT, 0); 

    if (p > 0) { 
     for (int i = 0; i < MAX_CLIENT_COUNT; i++) { 
      /* Has the pipe closed? */ 
      if ((PT.clientfds[i].revents & POLLHUP) != 0) { 
       // printf("[%d] done\n", PT.clientpids[i]); 
       waitpid(PT.clientpids[i], NULL, 0); 
       remove_process_from_table(i); 
      } 
     } 
    } 
} 

/** 
* Simulates waiting for a new client to connect. 
*/ 
void accept() { 
    sleep((rand() % 4) + 1); 
} 

/** 
* Simulates useful work being done by the child process, then exiting. 
*/ 
void childfunction() { 
    sleep((rand() % 10) + 1); 
    exit(0); 
} 

/** 
* Main program 
*/ 
int main() { 
    /* Initialize the process table */ 
    initialize_table(); 

    while (1) { 
     accept(); 

     /* Create the pipe */ 
     int p[2]; 
     pipe(p); 

     /* Fork off a child process. */ 
     pid_t cpid = fork(); 

     if (cpid == 0) { 
      /* Child process */ 
      close(p[0]); 
      childfunction(); 
     } 
     else { 
      /* Parent process */ 
      close(p[1]); 
      int i = get_next_available_entry(); 
      add_process_to_table(i, cpid, p[0]); 
      // printf("[%d] started\n", cpid); 
      reap_zombie_processes(); 
     } 
    } 

    return 0; 
} 

这里是从与printf语句注释掉运行该程序的一些示例输出:

[31066] started 
[31067] started 
[31068] started 
[31069] started 
[31066] done 
[31070] started 
[31067] done 
[31068] done 
[31071] started 
[31069] done 
[31072] started 
[31070] done 
[31073] started 
[31074] started 
[31072] done 
[31075] started 
[31071] done 
[31074] done 
[31081] started 
[31075] done