一、前言html
以前在看《unix環境高級編程》第八章進程時候,提到孤兒進程和殭屍進程,一直對這兩個概念比較模糊。今天被人問到什麼是孤兒進程和殭屍進程,會帶來什麼問題,怎麼解決,我只停留在概念上面,沒有深刻,倍感慚愧。晚上回來google了一下,再次參考APUE,認真總結一下,加深理解。linux
二、基本概念編程
咱們知道在unix/linux中,正常狀況下,子進程是經過父進程建立的,子進程在建立新的進程。子進程的結束和父進程的運行是一個異步過程,即父進程永遠沒法預測子進程 到底何時結束。 當一個 進程完成它的工做終止以後,它的父進程須要調用wait()或者waitpid()系統調用取得子進程的終止狀態。數據結構
孤兒進程:一個父進程退出,而它的一個或多個子進程還在運行,那麼那些子進程將成爲孤兒進程。孤兒進程將被init進程(進程號爲1)所收養,並由init進程對它們完成狀態收集工做。異步
殭屍進程:一個進程使用fork建立子進程,若是子進程退出,而父進程並無調用wait或waitpid獲取子進程的狀態信息,那麼子進程的進程描述符仍然保存在系統中。這種進程稱之爲僵死進程。函數
三、問題及危害post
unix提供了一種機制能夠保證只要父進程想知道子進程結束時的狀態信息, 就能夠獲得。這種機制就是: 在每一個進程退出的時候,內核釋放該進程全部的資源,包括打開的文件,佔用的內存等。 可是仍然爲其保留必定的信息(包括進程號the process ID,退出狀態the termination status of the process,運行時間the amount of CPU time taken by the process等)。直到父進程經過wait / waitpid來取時才釋放。 但這樣就致使了問題,若是進程不調用wait / waitpid的話, 那麼保留的那段信息就不會釋放,其進程號就會一直被佔用,可是系統所能使用的進程號是有限的,若是大量的產生僵死進程,將由於沒有可用的進程號而致使系統不能產生新的進程. 此即爲殭屍進程的危害,應當避免。測試
孤兒進程是沒有父進程的進程,孤兒進程這個重任就落到了init進程身上,init進程就好像是一個民政局,專門負責處理孤兒進程的善後工做。每當出現一個孤兒進程的時候,內核就把孤 兒進程的父進程設置爲init,而init進程會循環地wait()它的已經退出的子進程。這樣,當一個孤兒進程淒涼地結束了其生命週期的時候,init進程就會表明黨和政府出面處理它的一切善後工做。所以孤兒進程並不會有什麼危害。ui
任何一個子進程(init除外)在exit()以後,並不是立刻就消失掉,而是留下一個稱爲殭屍進程(Zombie)的數據結構,等待父進程處理。這是每一個 子進程在結束時都要通過的階段。若是子進程在exit()以後,父進程沒有來得及處理,這時用ps命令就能看到子進程的狀態是「Z」。若是父進程能及時 處理,可能用ps命令就來不及看到子進程的殭屍狀態,但這並不等於子進程不通過殭屍狀態。 若是父進程在子進程結束以前退出,則子進程將由init接管。init將會以父進程的身份對殭屍狀態的子進程進行處理。google
殭屍進程危害場景:
例若有個進程,它按期的產 生一個子進程,這個子進程須要作的事情不多,作完它該作的事情以後就退出了,所以這個子進程的生命週期很短,可是,父進程只管生成新的子進程,至於子進程 退出以後的事情,則一律漠不關心,這樣,系統運行上一段時間以後,系統中就會存在不少的僵死進程,假若用ps命令查看的話,就會看到不少狀態爲Z的進程。 嚴格地來講,僵死進程並非問題的根源,罪魁禍首是產生出大量僵死進程的那個父進程。所以,當咱們尋求如何消滅系統中大量的僵死進程時,答案就是把產生大 量僵死進程的那個元兇槍斃掉(也就是經過kill發送SIGTERM或者SIGKILL信號啦)。槍斃了元兇進程以後,它產生的僵死進程就變成了孤兒進 程,這些孤兒進程會被init進程接管,init進程會wait()這些孤兒進程,釋放它們佔用的系統進程表中的資源,這樣,這些已經僵死的孤兒進程 就能瞑目而去了。
三、孤兒進程和殭屍進程測試
孤兒進程測試程序以下所示:
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 //建立一個進程 10 pid = fork(); 11 //建立失敗 12 if (pid < 0) 13 { 14 perror("fork error:"); 15 exit(1); 16 } 17 //子進程 18 if (pid == 0) 19 { 20 printf("I am the child process.\n"); 21 //輸出進程ID和父進程ID 22 printf("pid: %d\tppid:%d\n",getpid(),getppid()); 23 printf("I will sleep five seconds.\n"); 24 //睡眠5s,保證父進程先退出 25 sleep(5); 26 printf("pid: %d\tppid:%d\n",getpid(),getppid()); 27 printf("child process is exited.\n"); 28 } 29 //父進程 30 else 31 { 32 printf("I am father process.\n"); 33 //父進程睡眠1s,保證子進程輸出進程id 34 sleep(1); 35 printf("father process is exited.\n"); 36 } 37 return 0; 38 }
測試結果以下:
殭屍進程測試程序以下所示:
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 //等待子進程先退出 22 sleep(2); 23 //輸出進程信息 24 system("ps -o pid,ppid,state,tty,command"); 25 printf("father process is exiting.\n"); 26 return 0; 27 }
測試結果以下所示:
殭屍進程測試2:父進程循環建立子進程,子進程退出,形成多個殭屍進程,程序以下所示:
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 //循環建立子進程 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 //子進程退出,成爲殭屍進程 22 exit(0); 23 } 24 else 25 { 26 //父進程休眠20s繼續建立子進程 27 sleep(20); 28 continue; 29 } 30 } 31 return 0; 32 }
程序測試結果以下所示:
四、殭屍進程解決辦法
(1)經過信號機制
子進程退出時向父進程發送SIGCHILD信號,父進程處理SIGCHILD信號。在信號處理函數中調用wait進行處理殭屍進程。測試程序以下所示:
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 //建立捕捉子進程退出信號 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 //等待子進程先退出 27 sleep(2); 28 //輸出進程信息 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 //處理殭屍進程 39 while ((pid = waitpid(-1, &stat, WNOHANG)) >0) 40 printf("child %d terminated.\n", pid); 41 }
測試結果以下所示:
(2)fork兩次
《Unix 環境高級編程》8.6節說的很是詳細。原理是將子進程成爲孤兒進程,從而其的父進程變爲init進程,經過init進程能夠處理殭屍進程。測試程序以下所示:
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 //建立第一個子進程 10 pid = fork(); 11 if (pid < 0) 12 { 13 perror("fork error:"); 14 exit(1); 15 } 16 //第一個子進程 17 else if (pid == 0) 18 { 19 //子進程再建立子進程 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 //第一個子進程退出 28 else if (pid >0) 29 { 30 printf("first procee is exited.\n"); 31 exit(0); 32 } 33 //第二個子進程 34 //睡眠3s保證第一個子進程退出,這樣第二個子進程的父親就是init進程裏 35 sleep(3); 36 printf("I am the second child process.pid: %d\tppid:%d\n",getpid(),getppid()); 37 exit(0); 38 } 39 //父進程處理第一個子進程退出 40 if (waitpid(pid, NULL, 0) != pid) 41 { 42 perror("waitepid error:"); 43 exit(1); 44 } 45 exit(0); 46 return 0; 47 }
測試結果以下圖所示:
五、參考資料
《unix環境高級編程》第八章
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
摘自:http://www.cnblogs.com/Anker/p/3271773.html