1、前言
之前在看《unix環(huán)境高級編程》第八章進(jìn)程時候,提到孤兒進(jìn)程和僵尸進(jìn)程,一直對這兩個概念比較模糊。今天被人問到什么是孤兒進(jìn)程和僵尸進(jìn)程,會帶來什么問題,怎么解決,我只停留在概念上面,沒有深入,倍感慚愧。晚上回來google了一下,再次參考APUE,認(rèn)真總結(jié)一下,加深理解。
2、基本概念
我們知道在unix/linux中,正常情況下,子進(jìn)程是通過父進(jìn)程創(chuàng)建的,子進(jìn)程在創(chuàng)建新的進(jìn)程。子進(jìn)程的結(jié)束和父進(jìn)程的運(yùn)行是一個異步過程,即父進(jìn)程永遠(yuǎn)無法預(yù)測子進(jìn)程 到底什么時候結(jié)束。 當(dāng)一個 進(jìn)程完成它的工作終止之后,它的父進(jìn)程需要調(diào)用wait()或者waitpid()系統(tǒng)調(diào)用取得子進(jìn)程的終止?fàn)顟B(tài)。
孤兒進(jìn)程:一個父進(jìn)程退出,而它的一個或多個子進(jìn)程還在運(yùn)行,那么那些子進(jìn)程將成為孤兒進(jìn)程。孤兒進(jìn)程將被init進(jìn)程(進(jìn)程號為1)所收養(yǎng),并由init進(jìn)程對它們完成狀態(tài)收集工作。
僵尸進(jìn)程:一個進(jìn)程使用fork創(chuàng)建子進(jìn)程,如果子進(jìn)程退出,而父進(jìn)程并沒有調(diào)用wait或waitpid獲取子進(jìn)程的狀態(tài)信息,那么子進(jìn)程的進(jìn)程描述符仍然保存在系統(tǒng)中。這種進(jìn)程稱之為僵死進(jìn)程。
3、問題及危害
unix提供了一種機(jī)制可以保證只要父進(jìn)程想知道子進(jìn)程結(jié)束時的狀態(tài)信息, 就可以得到。這種機(jī)制就是: 在每個進(jìn)程退出的時候,內(nèi)核釋放該進(jìn)程所有的資源,包括打開的文件,占用的內(nèi)存等。 但是仍然為其保留一定的信息(包括進(jìn)程號the PRocess ID,退出狀態(tài)the termination status of the process,運(yùn)行時間the amount of CPU time taken by the process等)。直到父進(jìn)程通過wait / waitpid來取時才釋放。 但這樣就導(dǎo)致了問題,如果進(jìn)程不調(diào)用wait / waitpid的話, 那么保留的那段信息就不會釋放,其進(jìn)程號就會一直被占用,但是系統(tǒng)所能使用的進(jìn)程號是有限的,如果大量的產(chǎn)生僵死進(jìn)程,將因?yàn)闆]有可用的進(jìn)程號而導(dǎo)致系統(tǒng)不能產(chǎn)生新的進(jìn)程. 此即為僵尸進(jìn)程的危害,應(yīng)當(dāng)避免。
孤兒進(jìn)程是沒有父進(jìn)程的進(jìn)程,孤兒進(jìn)程這個重任就落到了init進(jìn)程身上,init進(jìn)程就好像是一個民政局,專門負(fù)責(zé)處理孤兒進(jìn)程的善后工作。每當(dāng)出現(xiàn)一個孤兒進(jìn)程的時候,內(nèi)核就把孤 兒進(jìn)程的父進(jìn)程設(shè)置為init,而init進(jìn)程會循環(huán)地wait()它的已經(jīng)退出的子進(jìn)程。這樣,當(dāng)一個孤兒進(jìn)程凄涼地結(jié)束了其生命周期的時候,init進(jìn)程就會代表黨和政府出面處理它的一切善后工作。因此孤兒進(jìn)程并不會有什么危害。
任何一個子進(jìn)程(init除外)在exit()之后,并非馬上就消失掉,而是留下一個稱為僵尸進(jìn)程(Zombie)的數(shù)據(jù)結(jié)構(gòu),等待父進(jìn)程處理。這是每個 子進(jìn)程在結(jié)束時都要經(jīng)過的階段。如果子進(jìn)程在exit()之后,父進(jìn)程沒有來得及處理,這時用ps命令就能看到子進(jìn)程的狀態(tài)是“Z”。如果父進(jìn)程能及時 處理,可能用ps命令就來不及看到子進(jìn)程的僵尸狀態(tài),但這并不等于子進(jìn)程不經(jīng)過僵尸狀態(tài)。 如果父進(jìn)程在子進(jìn)程結(jié)束之前退出,則子進(jìn)程將由init接管。init將會以父進(jìn)程的身份對僵尸狀態(tài)的子進(jìn)程進(jìn)行處理。
僵尸進(jìn)程危害場景:
例如有個進(jìn)程,它定期的產(chǎn) 生一個子進(jìn)程,這個子進(jìn)程需要做的事情很少,做完它該做的事情之后就退出了,因此這個子進(jìn)程的生命周期很短,但是,父進(jìn)程只管生成新的子進(jìn)程,至于子進(jìn)程 退出之后的事情,則一概不聞不問,這樣,系統(tǒng)運(yùn)行上一段時間之后,系統(tǒng)中就會存在很多的僵死進(jìn)程,倘若用ps命令查看的話,就會看到很多狀態(tài)為Z的進(jìn)程。 嚴(yán)格地來說,僵死進(jìn)程并不是問題的根源,罪魁禍?zhǔn)资钱a(chǎn)生出大量僵死進(jìn)程的那個父進(jìn)程。因此,當(dāng)我們尋求如何消滅系統(tǒng)中大量的僵死進(jìn)程時,答案就是把產(chǎn)生大 量僵死進(jìn)程的那個元兇槍斃掉(也就是通過kill發(fā)送SIGTERM或者SIGKILL信號啦)。槍斃了元兇進(jìn)程之后,它產(chǎn)生的僵死進(jìn)程就變成了孤兒進(jìn) 程,這些孤兒進(jìn)程會被init進(jìn)程接管,init進(jìn)程會wait()這些孤兒進(jìn)程,釋放它們占用的系統(tǒng)進(jìn)程表中的資源,這樣,這些已經(jīng)僵死的孤兒進(jìn)程 就能瞑目而去了。
3、孤兒進(jìn)程和僵尸進(jìn)程測試
孤兒進(jìn)程測試程序如下所示:
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <errno.h> 4 #include <unistd.h> 5 6 int main() 7 { 8 pid_t pid; 9 //創(chuàng)建一個進(jìn)程10 pid = fork();11 //創(chuàng)建失敗12 if (pid < 0)13 {14 perror("fork error:");15 exit(1);16 }17 //子進(jìn)程18 if (pid == 0)19 {20 printf("I am the child process./n");21 //輸出進(jìn)程ID和父進(jìn)程ID22 printf("pid: %d/tppid:%d/n",getpid(),getppid());23 printf("I will sleep five seconds./n");24 //睡眠5s,保證父進(jìn)程先退出25 sleep(5);26 printf("pid: %d/tppid:%d/n",getpid(),getppid());27 printf("child process is exited./n");28 }29 //父進(jìn)程30 else31 {32 printf("I am father process./n");33 //父進(jìn)程睡眠1s,保證子進(jìn)程輸出進(jìn)程id34 sleep(1);35 printf("father process is exited./n");36 }37 return 0;38 }測試結(jié)果如下:
僵尸進(jìn)程測試程序如下所示:
1 #include <stdio.h> 2 #include <unistd.h> 3 #include <errno.h> 4 #include <stdlib.h> 5 6 int main() 7 { 8 pid_t pid; 9 pid = fork();10 if (pid < 0)11 {12 perror("fork error:");13 exit(1);14 }15 else if (pid == 0)16 {17 printf("I am child process.I am exiting./n");18 exit(0);19 }20 printf("I am father process.I will sleep two seconds/n");21 //等待子進(jìn)程先退出22 sleep(2);23 //輸出進(jìn)程信息24 system("ps -o pid,ppid,state,tty,command");25 printf("father process is exiting./n");26 return 0;27 }測試結(jié)果如下所示:
僵尸進(jìn)程測試2:父進(jìn)程循環(huán)創(chuàng)建子進(jìn)程,子進(jìn)程退出,造成多個僵尸進(jìn)程,程序如下所示:
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <unistd.h> 4 #include <errno.h> 5 6 int main() 7 { 8 pid_t pid; 9 //循環(huán)創(chuàng)建子進(jìn)程10 while(1)11 {12 pid = fork();13 if (pid < 0)14 {15 perror("fork error:");16 exit(1);17 }18 else if (pid == 0)19 {20 printf("I am a child process./nI am exiting./n");21 //子進(jìn)程退出,成為僵尸進(jìn)程22 exit(0);23 }24 else25 {26 //父進(jìn)程休眠20s繼續(xù)創(chuàng)建子進(jìn)程27 sleep(20);28 continue;29 }30 }31 return 0;32 }程序測試結(jié)果如下所示:
4、僵尸進(jìn)程解決辦法
(1)通過信號機(jī)制
子進(jìn)程退出時向父進(jìn)程發(fā)送SIGCHILD信號,父進(jìn)程處理SIGCHILD信號。在信號處理函數(shù)中調(diào)用wait進(jìn)行處理僵尸進(jìn)程。測試程序如下所示:
1 #include <stdio.h> 2 #include <unistd.h> 3 #include <errno.h> 4 #include <stdlib.h> 5 #include <signal.h> 6 7 static void sig_child(int signo); 8 9 int main()10 {11 pid_t pid;12 //創(chuàng)建捕捉子進(jìn)程退出信號13 signal(SIGCHLD,sig_child);14 pid = fork();15 if (pid < 0)16 {17 perror("fork error:");18 exit(1);19 }20 else if (pid == 0)21 {22 printf("I am child process,pid id %d.I am exiting./n",getpid());23 exit(0);24 }25 printf("I am father process.I will sleep two seconds/n");26 //等待子進(jìn)程先退出27 sleep(2);28 //輸出進(jìn)程信息29 system("ps -o pid,ppid,state,tty,command");30 printf("father process is exiting./n");31 return 0;32 }33 34 static void sig_child(int signo)35 {36 pid_t pid;37 int stat;38 //處理僵尸進(jìn)程39 while ((pid = waitpid(-1, &stat, WNOHANG)) >0)40 printf("child %d terminated./n", pid);41 }測試結(jié)果如下所示:
(2)fork兩次 《Unix 環(huán)境高級編程》8.6節(jié)說的非常詳細(xì)。原理是將子進(jìn)程成為孤兒進(jìn)程,從而其的父進(jìn)程變?yōu)閕nit進(jìn)程,通過init進(jìn)程可以處理僵尸進(jìn)程。測試程序如下所示:
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <unistd.h> 4 #include <errno.h> 5 6 int main() 7 { 8 pid_t pid; 9 //創(chuàng)建第一個子進(jìn)程10 pid = fork();11 if (pid < 0)12 {13 perror("fork error:");14 exit(1);15 }16 //第一個子進(jìn)程17 else if (pid == 0)18 {19 //子進(jìn)程再創(chuàng)建子進(jìn)程20 printf("I am the first child process.pid:%d/tppid:%d/n",getpid(),getppid());21 pid = fork();22 if (pid < 0)23 {24 perror("fork error:");25 exit(1);26 }27 //第一個子進(jìn)程退出28 else if (pid >0)29 {30 printf("first procee is exited./n");31 exit(0);32 }33 //第二個子進(jìn)程34 //睡眠3s保證第一個子進(jìn)程退出,這樣第二個子進(jìn)程的父親就是init進(jìn)程里35 sleep(3);36 printf("I am the second child process.pid: %d/tppid:%d/n",getpid(),getppid());37 exit(0);38 }39 //父進(jìn)程處理第一個子進(jìn)程退出40 if (waitpid(pid, NULL, 0) != pid)41 {42 perror("waitepid error:");43 exit(1);44 }45 exit(0);46 return 0;47 }測試結(jié)果如下圖所示:
5、參考資料
《unix環(huán)境高級編程》第八章
http://www.rosoo.net/a/201109/15071.html
http://blog.chinaunix.net/uid-1829236-id-3166986.html
http://forkhope.diandian.com/post/2012-10-01/40040574200
http://blog.csdn.net/metasearch/article/details/2498853
http://blog.csdn.net/yuwenliang/article/details/6770750
冷靜思考,勇敢面對,把握未來!
新聞熱點(diǎn)
疑難解答
圖片精選