如果一个进程已经退出,但是占用的资源未被回收,则称这样的进程为僵尸进程。很容易想到,如果子进程退出后,父进程未用 wait() 或 waitpid() 去回收子进程的资源,就会形成僵尸进程。如果一个程序循环创建子进程,但忘记回收退出的子进程的资源。那么这些子进程就会一直占用进程号。而系统的进程号是有限的,这个程序长时间运行后就会耗尽系统的进程号,从而导致无法产生新的进程。
代码
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <stdlib.h>
int main()
{
pid_t pid = fork();
if(pid < 0)
{
perror("fork");
return 0;
}
if(pid == 0)
{
printf("子进程:%d\n",getpid());
/* 子进程退出 */
exit(0);
}
else if(pid > 0)
{
/* 父进程不使用 wait() 或 waitpid() */
printf("父进程:%d\n",getpid());
}
/* 查看系统中的僵尸进程 */
system("ps -ef | grep defunct");
return 0;
}
[lingyun@manjaro study]$ gcc study.c
[lingyun@manjaro study]$ ./a.out
父进程:11125
子进程:11126
lingyun 11126 11125 0 21:57 pts/1 00:00:00 [a.out] <defunct>
lingyun 11127 11125 0 21:57 pts/1 00:00:00 sh -c ps -ef | grep defunct
lingyun 11129 11127 0 21:57 pts/1 00:00:00 grep defunct
可以看到,子进程 11126 是一个僵尸进程。注意:如果父进程也结束了,那么子进程的资源就会被回收。(不清楚啥机制,实测得到的结果)
避免僵尸进程
当然,最直接的方式就是在父进程中使用 wait() 或 waitpid() 去回收子进程的资源。但这对父进程来说太浪费了。我们可以用信号机制来解决。子进程退出时会向父进程发送 SIGCHLD 信号,我们就可以使用 signal() 函数来处理它。只要子进程退出,就自动调用回调函数,在回调函数里调用 wait() 或 waitpid() 即可。
代码
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/wait.h>
void signal_callback(int signo)
{
pid_t pid;
/* 用 while 可以处理多个子进程退出 */
while((pid = waitpid(-1,NULL,WNOHANG)) > 0)
{
printf("回收的子进程为:%d\n",pid);
}
}
int main()
{
/* 设置捕捉子进程的信号,只有接收到信号 SIGCHLD 便自动调用 signal_callback 函数 */
signal(SIGCHLD,signal_callback);
pid_t pid = fork();
if(pid < 0)
{
perror("fork");
return -1;
}
if(pid == 0)
{
printf("子进程:%d\n",getpid());
exit(0);
}
else if(pid > 0)
{
/* 让子进程先结束 */
sleep(5);
printf("父进程:%d\n",getpid());
system("ps -ef | grep defunct");
}
return 0;
}
[lingyun@manjaro study]$ gcc study.c
[lingyun@manjaro study]$ ./a.out
子进程:16392
回收的子进程为:16392
父进程:16391
lingyun 16393 16391 0 22:30 pts/1 00:00:00 sh -c ps -ef | grep defunct
lingyun 16395 16393 0 22:30 pts/1 00:00:00 grep defunct
可以看到子进程退出后并未产生僵尸进程,说明资源已经被回收了。
如果子进程的状态对父进程没有用,那么可以用 signal(SIGCHLD, SIG_IGN) 通知内核去处理退出的子进程,回收资源,不会再向父进程发送信号。
代码
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/wait.h>
/*
void signal_callback(int signo)
{
pid_t pid;
while((pid = waitpid(-1,NULL,WNOHANG)) > 0)
{
printf("回收的子进程为:%d\n",pid);
}
}
*/
int main()
{
/* 设置捕捉子进程的信号,子进程结束后,内核会回收,并不再给父进程发送信号 */
signal(SIGCHLD,SIG_IGN);
pid_t pid = fork();
if(pid < 0)
{
perror("fork");
return -1;
}
if(pid == 0)
{
printf("子进程:%d\n",getpid());
exit(0);
}
else if(pid > 0)
{
/* 让子进程先结束 */
sleep(5);
printf("父进程:%d\n",getpid());
system("ps -ef | grep defunct");
}
return 0;
}
[lingyun@manjaro study]$ gcc study.c
[lingyun@manjaro study]$ ./a.out
子进程:18167
父进程:18166
lingyun 18168 18166 0 22:41 pts/1 00:00:00 sh -c ps -ef | grep defunct
lingyun 18170 18168 0 22:41 pts/1 00:00:00 grep defunct
可见,并未产生僵尸进程,这样的处理方式也可以实现僵尸进程的避免。
还有一种方式可以做到这一点,就是制造孤儿进程,孤儿进程的父进程是 init 进程(1号进程),这样子进程退出后,init 进程就会回收其资源,同样也可避免僵尸进程。今儿太累了,不学了,吃不消,撤了。