孤儿进程和僵尸进程常见面试知识点
2018-03-19 21:07
369 查看
1、基本概念
我们知道在linux中,正常情况下,子进程是通过父进程创建的,子进程再创建新的进程。子进程的结束和父进程的运行是一个异步过程,即父进程永远无法预测子进程到底什么时候结束。当一个进程完成它的工作终止之后,它的父进程需要调用wait()或者waitpid()取得子进程的终止状态。孤儿进程:一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集等工作。
僵尸进程:一个进程使用fork创建子进程,如果子进程退出,而父进程并没有调用wait或waitpid获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中,这种进程称之为僵尸进程。
2、问题及危害
Linux提供了一种机制可以保证只要父进程想知道子进程结束时的状态信息,就可以得到。这种机制就是: 在每个进程退出的时候,内核释放该进程所有的资源,包括打开的文件,占用的内存等。 但是仍然为其保留一定的信息(包括进程号,退出状态,运行时间等)。直到父进程通过wait/ waitpid来取时才释放。但这样就导致了问题,如果进程不调用wait / waitpid的话, 那么保留的那段信息就不会释放,其进程号就会一直被占用,但是系统所能使用的进程号是有限的,如果大量的产生僵死进程,将因为没有可用的进程号而导致系统不能产生新的进程. 此即为僵尸进程的危害,应当避免。
孤儿进程是没有父进程的进程,孤儿进程这个重任就落到了init进程身上,init进程就好像是一个福利院,专门负责处理孤儿进程的善后工作。每当出现一个孤儿进程的时候,内核就把孤儿进程的父进程设置为init,这样,当一个孤儿进程结束了其生命周期的时候,init进程就会代表党和政府出面处理它的一切善后工作,因此孤儿进程并不会有什么危害。
任何一个子进程(init除外)在exit()之后,并非马上就消失掉,而是留下一个称为僵尸进程的数据结构,等待父进程处理。这是每个子进程在结束时都要经过的阶段。如果子进程在exit()之后,父进程没有来得及处理,这时用ps命令就能看到子进程的状态是“Z”。如果父进程能及时处理,可能用ps命令就来不及看到子进程的僵尸状态,但这并不等于子进程不经过僵尸状态。
如果父进程在子进程结束之前退出,则子进程将由init接管,init将会以父进程的身份对僵尸状态的子进程进行处理。
僵尸进程危害场景:
例如有个进程,它定期的产生一个子进程,这个子进程需要做的事情很少,做完它该做的事情之后就退出了,因此这个子进程的生命周期很短,但是,父进程只管生成新的子进程,至于子进程退出之后的事情,则一概的不闻不问,这样,系统运行一段时间之后,系统中就会存在很多的僵尸进程,倘若用ps命令查看的话,就会看到很多状态为“Z”的进程。 严格的来说,僵尸进程并不是问题的根源,罪魁祸首是产生出大量僵尸进程的那个父进程。因此,当我们寻求如何消灭系统中大量的僵尸进程时,答案就是把产生大量僵尸进程的那个元凶杀掉(也就是通过kill 发送SIGTERM或者SIGKILL信号)。杀了这个元凶进程之后,它产生的僵死进程就变成了孤儿进程,这些孤儿进程会被init进程接管,init进程会 wait()这些孤儿进程,释放它们占用的系统进程表中的资源。这样,这些已经僵尸的孤儿进程就能瞑目而去了。
3、孤儿进程和僵尸进程代码测试
孤儿进程测试程序如下所示:#include<stdio.h> #include<stdlib.h> #include<unistd.h> int main() { pid_t pid = fork();//创建一个进程 if(pid<0)//fork失败 { perror("fork"); exit(1); } if(pid == 0)//子进程 { printf("I am child process.\n"); printf("pid:%d ppid:%d\n",getpid(),getppid()); printf("I will sleep five seconds.\n"); sleep(5);//睡眠5秒保证父进程先退出 printf("pid:%d ppid:%d\n",getpid(),getppid()); printf("child process is exited.\n"); } else//父进程 { printf("I am father process.\n"); sleep(1);//睡眠1秒保证子进程输出进程id printf("father process is exited.\n"); } return 0; }
运行结果如下:
僵尸进程测试程序如下所示:
#include<stdio.h> #include<unistd.h> #include<stdlib.h> int main() { pid_t pid = fork(); if(pid < 0) { perror("fork"); exit(1); } if(pid == 0) { printf("I am child process.I am exiting.\n"); exit(0); } else { printf("I am father process.I will sleep five seconds.\n"); sleep(5); system("ps -o pid,ppid,state,tty,command"); printf("father process is exiting.\n"); } return 0; }
运行结果如下:
4、僵尸进程解决办法
(1)通过信号机制子进程退出时向父进程发送SIGCHILD信号,父进程处理SIGCHILD信号。在信号函数中调用wait进行处理僵尸进程。测试程序如下所示:
#include<stdio.h> #include<unistd.h> #include<stdlib.h> #include<signal.h> static void sig_child(int signo) { pid_t pid; int stat; while((pid = waitpid(-1,&stat,WNOHANG))>0) { printf("child %d terminated.\n",pid); } } int main() { signal(SIGCHLD,sig_child); pid_t pid = fork(); if(pid<0) { perror("fork"); exit(1); } if(pid == 0) { printf("I am child process,pid id %d.I am exiting.\n",getpid()); exit(0); } else { printf("I am father process.I will sleep five seconds.\n"); sleep(5); system("ps -o pid,ppid,state,command"); printf("father process is exiting.\n"); } return 0; }
运行结果如下:
(2)fork两次
原理就是将子进程成为孤儿进程,从而其的父进程变为init进程,通过init进程来处理僵尸进程。
测试程序如下所示:
#include<stdio.h> #include<unistd.h> #include<stdlib.h> int main() { pid_t pid = fork(); if(pid < 0) { perror("fork"); exit(1); } if(pid == 0) { printf("I am the first child process.pid:%d ppid:%d\n",getpid(),getppid()); pid = fork(); if(pid < 0) { perror("fork"); exit(1); } if(pid > 0) { printf("first proess is exited.\n"); exit(0); } sleep(3); printf("I am the second child process.pid:%d ppid:%d\n",getpid(),getppid()); exit(0); } if(waitpid(pid,NULL,0) != pid) { perror("waitpid"); exit(1); } exit(0); return 0; }
运行结果如图所示:
若有错误,望评论指正,我们共同进步!
相关文章推荐