概念
当子进程退出时,退出状态信息保存在内核中。子进程的进程ID同样也保存在系统的进程列表中。这时的进程被称为僵尸进程。僵尸进程占用的内存是微乎其微的,不能被系统调度,也不会占用cpu时间。僵尸进程会一直存在于系统中,知道父进程得到其结束状态信息。
终端中输入:ps -ef 带[]的进程为僵尸进程
僵尸进程的危害
僵尸进程保存了进程ID,所以对于系统而言僵尸进程是一个确实存在的进程,因此新的进程不能使用此ID。Linux下死机有严格的进程数限制的,当僵尸进程达到一定数目是,系统将不能再产生新的进程。
避免产生僵尸进程
1、父进程等待子进程退
父进程中使用下面两种方式等待子进程退出:
#include <sys/wait.h>
pid_t wait(int *statloc);
pit_t waitpid(pid_t pid, int * statloc, int options);
2、托孤
为了避免僵尸进程,父进程就必须先获取子进程的退出状态。当时这一点玩玩很难做到。
以一个tcp服务器程序为例:
1、当父进程使用了阻塞的方式来获取退出信息,那么这个服务器就只能处理一个链接请求
2、当父进程使用非阻塞等待进程退出方式,又会在accept()函数发生阻塞。当进程退出时又没有新的客户端连接,那么必然会存在一个僵尸进程
...
...
...
for(;;){
socklen_t addrlen = sizeof(struct sockaddr);
sc = accept(ss,(struct sockaddr *)&client_addr,&addrlen);
/* 接收客戶端连接 */
if(sc < 0){
printf("sc = %d\n",sc);
continue;
}
/* 建立一个新的进程处理到来的连接 */
pid[i] = fork();
if(pid[i] == 0){ /* 子进程 */
close(ss); /* 在子进程重关闭服务器的侦听 */
process_conn_server(sc);
exit(0);
}else{
close(sc); /* 父进程关闭客户端连接 */
// waitpid(pid[i],NULL,WNOHANG);
wait(0); //这里发生阻塞,必须等待子进程退出后wait才能返回,父进程才能再处理新的连接
}
}
...
...
...
孤儿进程的领养者——init
当父进程在子进程之前结束,此时的子进程称为孤儿进程。linux中init进程负责领养所有孤儿进程。也就是说父进程先与子进程结束,init将称为该子进程的父进程。init进程被设计成永远调用wait()函数,init的子进程永远不会称为僵尸进程
tcp服务器软件设计
根据init领养孤儿进程的原理,将上面的代码重新设计:主进程创建子进程-》子进程在创建孙子进程-》子进程退出。此时的孙子进程就会被init领养。孙子进程不会成为僵尸进程存在进程列表中