如果不让父进程等到子进程被杀,我怎样才能追踪子进程的死亡?
我正在尝试一种客户端 - 服务器方案,其中服务器接受来自客户端的连接,并为它接受的每个连接分叉一个新进程。
我忽略了SIGCHLD信号以防止僵尸创建。
signal(SIGCHLD, SIG_IGN);
while(1)
{
accept();
clients++;
if(fork() ==0)
{
childfunction();
clients--;
}
else
{
}
}
上述场景中的问题是,如果子进程在childfunction()
函数中被终止,则全局变量clients
不会减少。
注意:我正在寻找不使用SIGCHLD信号的解决方案......如果可能的话
答案 0 :(得分:26)
通常,您为SIGCHLD
编写了一个处理程序,该处理程序在pid waitpid()
上调用-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);
或者,您可以使用指定的子进程ID调用waitpid(pid, &status, 0)
,并同步等待它死亡。或者使用WNOHANG
检查其状态而不会阻止。
答案 1 :(得分:6)
到目前为止,没有一个解决方案提供了一种不使用SIGCHLD作为问题请求的方法。以下是poll中概述的使用this answer的替代方法的实现(这也解释了为什么在这种情况下应避免使用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
答案 2 :(得分:2)
你不想要一个僵尸。如果子进程死亡且父进程仍在运行但从未发出wait()
/ waitpid()
调用以获取状态,则系统不会释放与该子进程关联的资源,并且僵尸/已停止进程是留在proc表中。
尝试将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 */
...
}
}
在使用SIGCHLD
执行信号处理程序期间,您可以选择屏蔽/阻止其他sigprocmask()
信号。当信号处理例程完成时,必须将阻塞的掩码返回到其原始值。
如果你真的不想使用SIGCHLD
处理程序,你可以尝试将子处理循环添加到定期调用的地方,并轮询已终止的子代。
答案 3 :(得分:0)
变量'clients'位于fork()之后的不同进程地址空间中,当你减少子进程中的变量时,这不会影响父进程中的值。我认为您需要处理SIGCHLD以正确处理计数。