孤兒程序與殭屍程序
1、前言
之前在看《unix環境高階程式設計》第八章程序時候,提到孤兒程序和殭屍程序,一直對這兩個概念比較模糊。今天被人問到什麼是孤兒程序和殭屍程序,會帶來什麼問題,怎麼解決,我只停留在概念上面,沒有深入,倍感慚愧。晚上回來google了一下,再次參考APUE,認真總結一下,加深理解。
2、基本概念
我們知道在unix/linux中,正常情況下,子程序是通過父程序建立的,子程序在建立新的程序。子程序的結束和父程序的執行是一個非同步過程,即父程序永遠無法預測子程序 到底什麼時候結束。 當一個 程序完成它的工作終止之後,它的父程序需要呼叫wait()或者waitpid()系統呼叫取得子程序的終止狀態。
孤兒程序:一個父程序退出,而它的一個或多個子程序還在執行,那麼那些子程序將成為孤兒程序。孤兒程序將被init程序(程序號為1)所收養,並由init程序對它們完成狀態收集工作。
殭屍程序:一個程序使用fork建立子程序,如果子程序退出,而父程序並沒有呼叫wait或waitpid獲取子程序的狀態資訊,那麼子程序的程序描述符仍然儲存在系統中。這種程序稱之為僵死程序。
3、問題及危害
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程序就會代表黨和政府出面處理它的一切善後工作。因此孤兒程序並不會有什麼危害。
任何一個子程序(init除外)在exit()之後,並非馬上就消失掉,而是留下一個稱為殭屍程序(Zombie)的資料結構,等待父程序處理。
殭屍程序危害場景:
例如有個程序,它定期的產 生一個子程序,這個子程序需要做的事情很少,做完它該做的事情之後就退出了,因此這個子程序的生命週期很短,但是,父程序只管生成新的子程序,至於子程序 退出之後的事情,則一概不聞不問,這樣,系統執行上一段時間之後,系統中就會存在很多的僵死程序,倘若用ps命令檢視的話,就會看到很多狀態為Z的程序。 嚴格地來說,僵死程序並不是問題的根源,罪魁禍首是產生出大量僵死程序的那個父程序。因此,當我們尋求如何消滅系統中大量的僵死程序時,答案就是把產生大 量僵死程序的那個元凶槍斃掉(也就是通過kill傳送SIGTERM或者SIGKILL訊號啦)。槍斃了元凶程序之後,它產生的僵死程序就變成了孤兒進 程,這些孤兒程序會被init程序接管,init程序會wait()這些孤兒程序,釋放它們佔用的系統程序表中的資源,這樣,這些已經僵死的孤兒程序 就能瞑目而去了。
3、孤兒程序和殭屍程序測試
孤兒程序測試程式如下所示:
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 }
程式測試結果如下所示:
4、殭屍程序解決辦法
(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 }
測試結果如下圖所示:
5、參考資料
《unix環境高階程式設計》第八章
冷靜思考,勇敢面對,把握未來!