我怎么能追踪到一个subprocess的死亡,而没有让父进程等到subprocess被杀呢?
我正在尝试一个客户端 – 服务器场景,其中服务器接受来自客户端的连接,并为其接受的每个连接分配一个新进程。
我忽略了SIGCHLD信号来防止僵尸的创build。
signal(SIGCHLD, SIG_IGN); while(1) { accept(); clients++; if(fork() ==0) { childfunction(); clients--; } else { } }
上述情况中的问题是,如果subprocess在childfunction()
函数中被childfunction()
,则全局variablesclients
不会减less。
注:我正在寻找一个解决scheme,而不使用SIGCHLD信号…如果可能的话
通常你写一个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
来检查它的状态而不被阻塞。
到目前为止,没有任何解决方案提供了一种不使用SIGCHLD作为问题请求的方法。 这里是一个使用民意调查的方法, 这个答案 (这也解释了为什么你应该避免在这种情况下使用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
你不想要僵尸。 如果一个子进程死亡,并且父进程仍然正在运行,但从不发出wait()
/ waitpid()
调用来获取状态,则系统不会释放与该子进程相关的资源,并且在进程中留下僵尸/停止进程表。
尝试改变你的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
处理程序,你可以尝试在定期调用的地方添加子处理循环,并轮询终止的子程序。
变量'clients'在fork()之后在不同的进程地址空间中,当你在子变量中递减变量时,这不会影响父变量的值。 我认为你需要处理SIGCHLD来正确处理计数。