如何處理殭屍進程

1.什麼是殭屍進程


        如果父進程在子進程之前終止,則所有的子進程的父進程都會改變爲init進程,我們稱這些進程由init進程領養。這時使用ps命令查看後可以看到子進程的父進程ppid已經變爲了1。


        而當子進程在父進程之前終止時,內核爲每個終止子進程保存了一定量的信息,所以當終止進程的父進程調用wait或waitpid時,可以得到這些信息。這些信息至少包括進程ID、該進程的終止狀態、以及該進程使用的CPU時間總量。其他的進程所使用的存儲區,打開的文件都會被內核釋放。


        一個已經終止、但是其父進程尚未對其進行善後處理(獲取終止子進程的有關信息,釋放它仍佔用的資源)的進程被稱爲殭屍進程。ps命令將殭屍進程的狀態打印爲Z。

   

        可以設想一下,比如一個web服務器端,假如每次接收到一個連接都創建一個子進程去處理,處理完畢後結束子進程。假如在父進程中沒有使用wait或waitpid函數進行善後,這些子進程將全部變爲殭屍進程,Linux系統的進程數一般有一個固定限制值,殭屍進程將會逐漸耗盡系統資源。

 

2.查看殭屍進程的例子


#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
 
int main(int argc, char **argv)
{
   pid_t pid;
   for (int i=0; i<5; i++) {
       if ((pid = fork()) < 0) {
           printf("fork error,%s\n", strerror(errno));
           return -1;
       }  
 
       /* child */
       if (pid == 0) {
           sleep(1);
           exit(0);
       }  
   }  
   /* parent */
   sleep(20);
   return 0;
}

 

編譯完成後,在執行程序的命令後加上“&”符號,表示讓當前程序在後臺運行。

 

之後輸入:

ps –e –o pid,ppid,stat,command|grep [程序名]

 

 

可以看到5個子進程都已經是殭屍進程了。

 

3.SIGCHLD信號和處理殭屍進程


        當子進程終止時,內核就會向它的父進程發送一個SIGCHLD信號,父進程可以選擇忽略該信號,也可以提供一個接收到信號以後的處理函數。對於這種信號的系統默認動作是忽略它。

        我們不希望有過多的殭屍進程產生,所以當父進程接收到SIGCHLD信號後就應該調用 wait 或 waitpid 函數對子進程進行善後處理,釋放子進程佔用的資源。

 

        下面是一個捕獲SIGCHLD信號以後使用wait函數進行處理的簡單例子:

 

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <signal.h>
#include <sys/wait.h>
 
void deal_child(int sig_no)
{
    wait(NULL);
}
 
int main(int argc, char **argv)
{
    signal(SIGCHLD, deal_child);
 
    pid_t pid;
    for (int i=0; i<5; i++) {
        if ((pid = fork()) < 0) {
            printf("fork error,%s\n",strerror(errno));
            return -1;
        }  
 
        /* child */
        if (pid == 0) {
            sleep(1);
            exit(0);
        }  
    }  
    /* parent */
    for(int i=0; i<100000; i++) {
        for (int j=0; j<100000; j++) {
            int temp = 0;
        }  
    }  
    return 0;
}

 

        同樣在後臺運行後使用ps命令查看進程狀態,結果如圖:


         


        發現創建的5個進程,有3個已經被徹底銷燬,但是還有2個仍然處於殭屍進程的狀態。


        這是因爲當5個進程同時終止的時候,內核都會向父進程發送SIGCHLD信號,而父進程此時有可能仍然處於信號處理的deal_child函數中,那麼在處理完之前,中間接收到的SIGCHLD信號就會丟失,內核並沒有使用隊列等方式來存儲同一種信號。

 

4.正確地處理殭屍進程的方法


         爲了解決上面出現的這種問題,我們需要使用waitpid函數。

         函數原型爲:


         pid_t waitpid(pid_t pid, int *statloc, int options);


         若成功則返回進程ID,如果設置爲非阻塞方式,返回0表示子進程狀態未改變,出錯時返回-1。

        

         options參數可以設置爲WNOHANG常量,表示waitpid不阻塞,如果由pid指定的子進程不是立即可用的,則立即返回0

 

        只需要修改一下SIGCHLD信號的處理函數即可:

 

void deal_child(int sig_no)
{
    for (;;) {
        if (waitpid(-1, NULL, WNOHANG) == 0)
            break;
    }  
}

 

        再次執行程序後使用ps命令查看,發現已經不會產生殭屍進程了。

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