C语言 跟踪子进程的死亡
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/2377811/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Tracking the death of a child process
提问by codingfreak
How could I track down the death of a child process without making the parent process wait until the child process got killed?
如何在不让父进程等到子进程被杀死的情况下追踪子进程的死亡?
I am trying a client-server scenario where the server accepts the connection from a client and forks a new process for each and every connection it accepts.
我正在尝试一个客户端 - 服务器场景,其中服务器接受来自客户端的连接并为其接受的每个连接分叉一个新进程。
I am ignoring SIGCHLD signals to prevent zombie creation.
我忽略了 SIGCHLD 信号以防止僵尸创建。
signal(SIGCHLD, SIG_IGN);
while(1)
{
accept();
clients++;
if(fork() ==0)
{
childfunction();
clients--;
}
else
{
}
}
The problem in the above scenario is that if the child process gets killed in the childfunction()function, the global variable clientsis not getting decremented.
上述场景中的问题是,如果子进程在childfunction()函数中被杀死,全局变量clients不会递减。
NOTE:I am looking for a solution without using SIGCHLD signal ... If possible
注意:我正在寻找不使用 SIGCHLD 信号的解决方案......如果可能的话
回答by asveikau
Typically you write a handler for SIGCHLDwhich calls waitpid()on pid -1. You can use the return value from that to determine what pid died. For example:
通常,您编写一个处理程序来SIGCHLD调用waitpid()pid -1。您可以使用它的返回值来确定哪个 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);
Alternatively you can call waitpid(pid, &status, 0)with the child's process ID specified, and synchronously wait for it to die. Or use WNOHANGto check its status without blocking.
或者,您可以waitpid(pid, &status, 0)使用指定的子进程 ID 调用,并同步等待它死亡。或用于WNOHANG在不阻塞的情况下检查其状态。
回答by Michael Harvey
None of the solutions so far offer an approach without using SIGCHLD as the question requests. Here is an implementation of an alternative approach using pollas outlined in this answer(which also explains why you should avoid using SIGCHLD in situations like this):
到目前为止,没有任何解决方案提供一种不使用 SIGCHLD 作为问题请求的方法。这是使用此答案中概述的轮询的替代方法的实现(这也解释了为什么在这种情况下应避免使用 SIGCHLD):
Make sure you have a pipe to/from each child process you create. It can be either their stdin/stdout/stderr or just an extra dummy fd. When the child process terminates, its end of the pipe will be closed, and your main event loop will detect the activity on that file descriptor. From the fact that it closed, you recognize that the child process died, and call waitpid to reap the zombie.
确保您有一个通往/来自您创建的每个子进程的管道。它可以是他们的 stdin/stdout/stderr 或者只是一个额外的虚拟 fd。当子进程终止时,它的管道末端将关闭,您的主事件循环将检测该文件描述符上的活动。从它关闭的事实来看,您认识到子进程已死,并调用 waitpid 来收割僵尸。
(Note: I omitted some best practices like error-checking and cleaning up file descriptors for brevity)
(注意:为简洁起见,我省略了一些最佳实践,例如错误检查和清理文件描述符)
/**
* 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;
}
And here is some sample output from running the program with the printfstatements uncommented:
以下是在未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
回答by jschmier
You don't want a zombie. If a child process dies and the parent is still RUNNING but never issues a wait()/waitpid()call to harvest the status, the system does not release the resources associated with the child and a zombie/defunct process is left in the proc table.
你不想要僵尸。如果子进程死亡并且父进程仍在运行但从未发出wait()/waitpid()调用来获取状态,则系统不会释放与子进程关联的资源,并且在 proc 表中会留下一个僵死/已失效的进程。
Try changing your SIGCHLDhandler to something closer to the following:
尝试将您的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 */
...
}
}
You could optionally mask/block additional SIGCHLDsignals during execution of the signal handler using sigprocmask(). The blocked mask must be returned to its original value when the signal handling routine has finished.
您可以选择SIGCHLD在信号处理程序执行期间使用sigprocmask(). 当信号处理例程完成时,阻塞掩码必须返回到其原始值。
If you really don't want to use a SIGCHLDhandler, you could try adding the child processing loop somewhere where it would be called regularly and poll for terminated children.
如果你真的不想使用SIGCHLD处理程序,你可以尝试在某个地方添加子处理循环,在那里它会被定期调用并轮询终止的孩子。
回答by trojanfoe
The variable 'clients' are in different process address spaces after fork() and when you decrement the variable in the child, this will not affect the value in the parent. I think you need to handle SIGCHLD to handle the count correctly.
在 fork() 之后变量“clients”位于不同的进程地址空间中,当您递减子进程中的变量时,这不会影响父进程中的值。我认为您需要处理 SIGCHLD 才能正确处理计数。

