殭屍進程和僵死進程有什麼區別?


基本概念

  我們知道在unix/linux中,正常情況下,子進程是通過父進程創建的,子進程在創建新的進程。子進程的結束和父進程的運行是一個非同步過程,即父進程永遠無法預測子進程 到底什麼時候結束。 當一個 進程完成它的工作終止之後,它的父進程需要調用wait()或者waitpid()系統調用取得子進程的終止狀態。

  孤兒進程:一個父進程退出,而它的一個或多個子進程還在運行,那麼那些子進程將成為孤兒進程。孤兒進程將被init進程(進程號為1)所收養,並由init進程對它們完成狀態收集工作。

  殭屍進程:一個進程使用fork創建子進程,如果子進程退出,而父進程並沒有調用wait或waitpid獲取子進程的狀態信息,那麼子進程的進程描述符仍然保存在系統中。這種進程稱之為僵死進程。

問題及危害

  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)的數據結構,等待父進程處理。這是每個 子進程在結束時都要經過的階段。如果子進程在exit()之後,父進程沒有來得及處理,這時用ps命令就能看到子進程的狀態是「Z」。如果父進程能及時 處理,可能用ps命令就來不及看到子進程的殭屍狀態,但這並不等於子進程不經過殭屍狀態。 如果父進程在子進程結束之前退出,則子進程將由init接管。init將會以父進程的身份對殭屍狀態的子進程進行處理。

  殭屍進程危害場景:

  例如有個進程,它定期的產 生一個子進程,這個子進程需要做的事情很少,做完它該做的事情之後就退出了,因此這個子進程的生命周期很短,但是,父進程只管生成新的子進程,至於子進程 退出之後的事情,則一概不聞不問,這樣,系統運行上一段時間之後,系統中就會存在很多的僵死進程,倘若用ps命令查看的話,就會看到很多狀態為Z的進程。 嚴格地來說,僵死進程並不是問題的根源,罪魁禍首是產生出大量僵死進程的那個父進程。因此,當我們尋求如何消滅系統中大量的僵死進程時,答案就是把產生大 量僵死進程的那個元兇槍斃掉(也就是通過kill發送SIGTERM或者SIGKILL信號啦)。槍斃了元兇進程之後,它產生的僵死進程就變成了孤兒進 程,這些孤兒進程會被init進程接管,init進程會wait()這些孤兒進程,釋放它們佔用的系統進程表中的資源,這樣,這些已經僵死的孤兒進程 就能瞑目而去了。

孤兒進程和殭屍進程測試

孤兒進程測試程序如下所示:

1 #include &
2 #include &
3 #include &
4 #include &
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. "); 21 //輸出進程ID和父進程ID 22 printf("pid: %d ppid:%d ",getpid(),getppid()); 23 printf("I will sleep five seconds. "); 24 //睡眠5s,保證父進程先退出 25 sleep(5); 26 printf("pid: %d ppid:%d ",getpid(),getppid()); 27 printf("child process is exited. "); 28 } 29 //父進程 30 else 31 { 32 printf("I am father process. "); 33 //父進程睡眠1s,保證子進程輸出進程id 34 sleep(1); 35 printf("father process is exited. "); 36 } 37 return 0; 38 }

測試結果如下:

殭屍進程測試程序如下所示:

1 #include &
2 #include &
3 #include &
4 #include &
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. "); 18 exit(0); 19 } 20 printf("I am father process.I will sleep two seconds "); 21 //等待子進程先退出 22 sleep(2); 23 //輸出進程信息 24 system("ps -o pid,ppid,state,tty,command"); 25 printf("father process is exiting. "); 26 return 0; 27 }

測試結果如下所示:

殭屍進程測試2:父進程循環創建子進程,子進程退出,造成多個殭屍進程,程序如下所示:

1 #include &
2 #include &
3 #include &
4 #include &
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. I am exiting. "); 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 &
2 #include &
3 #include &
4 #include &
5 #include &
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. ",getpid()); 23 exit(0); 24 } 25 printf("I am father process.I will sleep two seconds "); 26 //等待子進程先退出 27 sleep(2); 28 //輸出進程信息 29 system("ps -o pid,ppid,state,tty,command"); 30 printf("father process is exiting. "); 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.
", pid);
41 }

測試結果如下所示:

(2)fork兩次

  《Unix 環境高級編程》8.6節說的非常詳細。原理是將子進程成為孤兒進程,從而其的父進程變為init進程,通過init進程可以處理殭屍進程。測試程序如下所示:

1 #include &
2 #include &
3 #include &
4 #include &
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 ppid:%d ",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.
");
31 exit(0);
32 }
33 //第二個子進程
34 //睡眠3s保證第一個子進程退出,這樣第二個子進程的父親就是init進程里
35 sleep(3);
36 printf("I am the second child process.pid: %d ppid:%d
",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 }

測試結果如下圖所示:


你想更深入了解學習Linux知識體系,你可以看一下我們花費了一個多月整理了上百小時的幾百個知識點體系內容:

【超全整理】《Linux雲計算從入門到精通》系列實戰筆記全放送


在Unix系統編程中,常常會碰到兩個概念:殭屍進程和孤兒進程。話說我以前曾經把這兩個概念弄混淆過。

PS:實在不知道題主說的僵死進程和殭屍進程是什麼東西?

什麼是殭屍進程?

Unix進程模型中,進程是按照父進程產生子進程,子進程產生子子進程這樣的方式創建出完成各項相互協作功能的進程的。當一個進程完成它的工作終止之後,它的父進程需要調用wait()或者waitpid()系統調用取得子進程的終止狀態。如果父進程沒有這麼做的話,會產生什麼後果呢?此時,子進程雖然已經退出了,但是在系統進程表中還為它保留了一些退出狀態的信息,如果父進程一直不取得這些退出信息的話,這些進程表項就將一直被佔用,此時,這些占著茅坑不拉屎的子進程就成為「殭屍進程」(zombie)。系統進程表是一項有限資源,如果系統進程表被殭屍進程耗盡的話,系統就可能無法創建新的進程。

那麼,孤兒進程又是怎麼回事呢?

孤兒進程是指這樣一類進程:在進程還未退出之前,它的父進程就已經退出了,一個沒有了父進程的子進程就是一個孤兒進程(orphan)。既然所有進程都必須在退出之後被wait()或waitpid()以釋放其遺留在系統中的一些資源,那麼應該由誰來處理孤兒進程的善後事宜呢?這個重任就落到了init進程身上,init進程就好像是一個民政局,專門負責處理孤兒進程的善後工作。每當出現一個孤兒進程的時候,內核就把孤兒進程的父進程設置為init,而init進程會循環地wait()它的已經退出的子進程。這樣,當一個孤兒進程「凄涼地」結束了其生命周期的時候,init進程就會代表黨和政府出面處理它的一切善後工作。

這樣來看,孤兒進程並不會有什麼危害,真正會對系統構成威脅的是殭屍進程。

那麼,什麼情況下殭屍進程會威脅系統的穩定呢?

設想有這樣一個父進程:它定期的產生一個子進程,這個子進程需要做的事情很少,做完它該做的事情之後就退出了,因此這個子進程的生命周期很短,但是,父進程只管生成新的子進程,至於子進程退出之後的事情,則一概不聞不問,這樣,系統運行上一段時間之後,系統中就會存在很多的殭屍進程,倘若用ps命令查看的話,就會看到很多狀態為Z的進程。

嚴格地來說,殭屍進程並不是問題的根源,罪魁禍首是產生出大量殭屍進程的那個父進程。

因此,當我們尋求如何消滅系統中大量的殭屍進程時,答案就是把產生大量殭屍進程的那個元兇槍斃掉(通過kill發送SIGTERM或者SIGKILL信號)。

槍斃了元兇進程之後,它產生的殭屍進程就變成了孤兒進程,這些孤兒進程會被init進程接管,init進程會wait()這些孤兒進程,釋放它們佔用的系統進程表中的資源,這樣,這些已經「殭屍」的孤兒進程就能瞑目而去了。

關於殭屍進程的防範,參考如何編寫unix 程序防止殭屍進程的出現? - 郭無心的回答


死鎖嗎?


題主說的僵死進程和殭屍進程是同一個東西,他們兩隻是因為不同的書籍在翻譯的過程中對同一個詞(zombie)的不同翻譯方法。

查閱了一下APUE第三版中文版翻譯為僵死。


沒聽說過僵死,

臨時搜索了下,

估計就是殭屍,


推薦閱讀:

Linux/Windows在多CPU時控制CPU使用率時表現為什麼不同?
你有可能買一台 Chromebook 么?
深度學習工具caffe在windows上的性能表現真的沒有在Linux上好嗎?
從 X86 到 ARM 的移植,為什麼 Windows 的兼容性看起來沒有像Linux、OS X那樣好?
web後端攻城獅要學linux嗎,一般要學到什麼水平呢?

TAG:Linux | Unix | 進程 | 系統進程 | 殭屍進程zombie |