Linux下如何避免殭屍進程的產生

Linux下如何避免殭屍進程的產生

1. 什麼是殭屍進程

    比如進程採用exit()退出的時候,操作系統會進行一些列的處理工作,包括關閉打開的文件描述符、佔用的內存等等,但是,操作系統也會爲該進程保留少量的信息,比如進程ID號等信息,因而佔用了系統的資源。在一種極端的情況下,檔殭屍進程過多的時候,佔用了大量的進程ID,系統將無法產生新的進程,相當於系統的資源被耗盡。
    所以,避免殭屍進程的產生具有極其重要的意義。一般來講避免殭屍進程主要包含以下幾種方法:

  1. 父進程使用wait()或者waitpid()之類的函數等待子進程退出
  2. 父進程先產生一個子進程,然後子進程再產生一個孫子進程,子進程在孫子進程之前退出。
  3. 使用信號函數sigaction爲SIGCHLD設置wait處理函數。

2. wait()函數

    wait()函數的使用最爲簡單,源代碼如圖所示:
    
    父進程創建子進程後30s調用wait()函數,等待子進程退出,回收子進程的資源,這也意味着子進程將會成爲殭屍進程30s-5s=25s。
    運行該程序後,打開終端,查看進程狀態,該圖顯示的有一個進程的標誌爲Z,表示該進程爲殭屍進程(Zombie)。   
    
    當父進程調用wait()函數後,子進程的資源被回收,殭屍進程的標誌被去掉了.如下圖所示:
    

3. 兩次fork()創建孫子進程

    2中的方法比較簡潔,但是有個問題就是子進程如果處理的時間比較長的話,主進程會被掛起。比如:

  1.     socket()
  2.     bind()
  3.     listen()
  4.     while(1)
  5.     {
  6.          accept()
  7.         if(fork()==0)
  8.         {
  9.             while(1)
  10.             {
  11.                 read()
  12.                 process()
  13.                 write()
  14.             }
  15.             close()
  16.             exit
  17.         }
  18.         //wait()///<如果這裏父進程進行wait()操作,則很有可能再此處掛起,而如果不進行wait()操作,則此處又產生了殭屍進程。
  19.     }


    對於這樣的情況可以採取連續fork()兩次的方法。簡而言之,首先父進程首先創建子進程,子進程創建孫子進程,由孫子進程處理事務,
而子進程再創建完孫子進程後,就退出。此時,孫子進程的父進程,也就是子進程退出了,因此孫子進程變爲了一個孤兒進程,Linux進程處理
孤兒的進程的方式,是init進程接管孤兒進程,而init進程的子進程不會成爲殭屍進程。
    所以上述的僞代碼可以寫爲:

  1.     if(fork() ==0 )
  2.     {
  3.             if(fork()==0)
  4.             {
  5.                     /*孫子進程在這裏處理事務*/
  6.                     process();
  7.                     close()
  8.                     exit()
  9.             }
  10.             else
  11.             {
  12.                 /*子進程再這裏退出,使得孫子進程成爲init進程的兒子,從而避免殭屍進程的產生*/
  13.                 close()
  14.                 exit()///<子進程在此退出
  15.             }
  16.     }
  17.     else
  18.     {
  19.           ///<爺爺進程進入下一輪的處理。
  20.     }


4. 採用信號量處理函數

    還有一種方法就是採用信號量處理函數來處理這種情況,該過程的代碼可以表示爲:

  1. #include    
  2. #include    
  3. #include    
  4. #include    
  5. #include    
  6. #include    
  7. int num_clients = 0;  
  8. int dead_clients = 0;  
  9. void sig_chld_handler(int sig) {  
  10.     pid_t pid;  
  11.     if (sig == SIGCHLD) {  
  12.         pid = wait(NULL);  
  13.         printf("A child dead, current child number: %d, id: %d/n", ++dead_clients, pid);  
  14.     }  
  15. }  
  16. int main(int argc, char **argv) {  
  17.     pid_t pid;  
  18.     signal(SIGCHLD, sig_chld_handler);  
  19.     for (int i = 0; i < 30; i++) {  
  20.         if ((pid = fork()) == 0) {  
  21.             exit(0);  
  22.         } else if (pid > 0) {  
  23.             printf("A child created, current child number: %d, id: %d/n", ++num_clients, pid);  
  24.         }  
  25.     }  
  26.     sleep(10);  
  27.     return 0;  
  28. }

    父進程首先註冊一個信號處理函數signal(SIGCHLD, sig_chld_handler),然後每當子進程退出的時候父進程都會受到SIGCHLD信號,
觸發sig_chld_handler()函數,調用wait()函數等待子進程的退出。

5.總結

上述三種方法都有好處,個人比較傾向於連續兩次fork(),這種方式我個人認爲是最爲簡潔的方式。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章