Linux 系統中殭屍進程

Linux 系統中殭屍進程和現實中殭屍(雖然我也沒見過)類似,雖然已經死了,但是由於沒人給它們收屍,還能四處走動。殭屍進程指的是那些雖然已經終止的進程,但仍然保留一些信息,等待其父進程爲其收屍。

殭屍進程如何產生的?

如果一個進程在其終止的時候,自己就回收所有分配給它的資源,系統就不會產生所謂的殭屍進程了。那麼我們說一個進程終止之後,還保留哪些信息?爲什麼終止之後還需要保留這些信息呢?

一個進程終止的方法很多,進程終止後有些信息對於父進程和內核還是很有用的,例如進程的ID號、進程的退出狀態、進程運行的CPU時間等。因此進程 在終止時,回收所有內核分配給它的內存、關閉它打開的所有文件等等,但是還會保留以上極少的信息,以供父進程使用。父進程可以使用 wait/waitpid 等系統調用來爲子進程收拾,做一些收尾工作。

因此,一個殭屍進程產生的過程是:父進程調用fork創建子進程後,子進程運行直至其終止,它立即從內存中移除,但進程描述符仍然保留在內存中(進程描述符佔有極少的內存空間)。子進程的狀態變成EXIT_ZOMBIE,並且向父進程發送SIGCHLD 信號,父進程此時應該調用 wait() 系 統調用來獲取子進程的退出狀態以及其它的信息。在 wait 調用之後,殭屍進程就完全從內存中移除。因此一個殭屍存在於其終止到父進程調用 wait 等函數這個時間的間隙,一般很快就消失,但如果編程不合理,父進程從不調用 wait 等系統調用來收集殭屍進程,那麼這些進程會一直存在內存中。

在 Linux 下,我們可以使用 ps 等命令查看系統中殭屍進程,殭屍進程的狀態標記爲‘Z’:
screenshot from 2013-10-17 22 36 17

產生一個殭屍進程

根據上面的描述,我們很容易去寫一個程序來產生殭屍進程,如下代碼:

#include <stdio.h>#include <sys/types.h>int main(){
    //fork a child process
    pid_t pid = fork();

    if (pid > 0)   //parent process
    {
        printf("in parent process, sleep for one miniute...zZ...\n");
        sleep(60);
        printf("after sleeping, and exit!\n");
    }
    else if (pid == 0)  
    {
        //child process exit, and to be a zombie process
        printf("in child process, and exit!\n");
        exit(0);
    }

    return 0;}

父進程並沒有寫 wait 等系統調用函數,因此在子進程退出之後變成殭屍進程,父進程並沒有爲其去收屍。我們使用下面命令編譯運行該進程,然後查看系統中進程狀態:

guohailin@guohailin:~/Documents$ gcc zombie.c -o zombie
guohailin@guohailin:~/Documents$ ./zombie 
in parent process, sleep for one miniute...zZ...
in child process, and exit!

# 打開另一個終端:
guohailin@guohailin:~$ ps aux | grep -w 'Z'
1000      2211  1.2  0.0      0     0 ?        Z    13:24   6:53 [chromium-browse] <defunct>
1000      4400  0.0  0.0      0     0 ?        Z    10月16   0:00 [fcitx] <defunct>
1000     10871  0.0  0.0      0     0 pts/4    Z+   22:32   0:00 [zombie] <defunct>

從上面可以看出,系統中多了一個殭屍進程。但如果等父進程睡眠醒來退出之後,我們再次查看系統進程信息,發現剛纔的殭屍進程不見了。

guohailin@guohailin:~/Documents$ ./zombie 
in parent process, sleep for one miniute...zZ...
in child process, and exit!
after sleeping, and exit!
guohailin@guohailin:~/Documents$ ps aux | grep -w 'Z'
1000      2211  1.2  0.0      0     0 ?        Z    13:24   6:53 [chromium-browse] <defunct>
1000      4400  0.0  0.0      0     0 ?        Z    10月16   0:00 [fcitx] <defunct>

這是爲什麼呢?父進程到死都也沒有爲其子進程收屍呀,怎麼父進程退出之後,那個殭屍進程就消失了呢?難道父進程在退出時會爲子進程收拾嗎?其實不 然....真正的原因是:父進程死掉之後,其所有子進程過繼給 init 進程,init 進程成爲該殭屍進程的新進程,init 進程會週期性地去調用 wait 系統調用來清除它的殭屍孩子。因此,你會發現上面例子中父進程死掉之後,殭屍進程也跟着消失,其實是 init 進程爲其收屍的!

怎樣避免殭屍進程的產生

不能使用 kill 後接 SIGKILL 信號這樣的命令像殺死普通進程一樣殺死殭屍進程,因爲殭屍進程是已經死掉的進程,它不能再接收任何信號。事實上,如果系統中殭屍進程並不多的話,我們也無需去消除它們,少數的殭屍進程並不會對系統的性能有什麼影響。

那麼在編程時,如果能避免系統中大量產生殭屍進程呢?根據上面描述的,子進程在終止時會向父進程發 SIGCHLD 信號,Linux 默認是忽略該信號的,我們可以顯示安裝該信號,在信號處理函數中調用 wait 等函數來爲其收屍,這樣就能避免殭屍進程長期存在於系統中了。示例代碼如下:

#include <stdio.h>#include <signal.h>#include <string.h>#include <sys/types.h>#include <sys/wait.h>sig_atomic_t child_exit_status;void clean_up_child_process(int signal_num){
    /* clean up child process */
    int status;
    wait (&status);

    /* store its exit status in a global variable */
    child_exit_status = status;}int main(){
    /* handle SIGCHLD by calling clean_up_child_process  */
    struct sigaction sigchild_action;
    memset(&sigchild_action, 0, sizeof(sigchild_action));
    sigchild_action.sa_handler = &clean_up_child_process;
    sigaction(SIGCHLD, &sigchild_action, NULL);

    /* fork a child, and let the child process dies before parent */
    pid_t c_pid;
    c_pid = fork();
    if (c_pid > 0)
    {
        printf("in parent process, and sleep for on mininute...zZ...\n");
        sleep(60);
    }
    else if(c_pid == 0)
    {
        printf("in child process, and exit now\n");
        exit(0);
    }
    else
    {
        printf("fork failed!\n");
    }

    return 0;}


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