1. 程式人生 > >Linux 殭屍程序如何處理

Linux 殭屍程序如何處理

Linux 允許程序查詢核心以獲得其父程序的 PID,或者其任何子程序的執行狀態。例如,程序可以建立一個子程序來執行特定的任務,然後呼叫諸如 wait() 這樣的一些庫函式檢查子程序是否終止。如果子程序已經終止,那麼,它的終止代號將告訴父程序這個任務是否已成功地完成。
為了遵循這些設計原則,不允許 Linux 核心在程序一終止後就丟棄包含在程序描述符欄位中的資料。只有父程序發出了與被終止的程序相關的 wait() 類系統呼叫之後,才允許這樣做。這就是引入僵死狀態的原因:儘管從技術上來說程序已死,但必須儲存它的描述符,直到父程序得到通知。
如果一個程序已經終止,但是它的父程序尚未呼叫 wait() 或 waitpid() 對它進行清理,這時的程序狀態稱為僵死狀態,處於僵死狀態的程序稱為殭屍程序(zombie process)。任何程序在剛終止時都是殭屍程序,正常情況下,殭屍程序都立刻被父程序清理了。

殭屍程序是如何產生的

為了觀察到殭屍程序,我們自己寫一個不正常的程式,父程序 fork 出子程序,子程序終止,而父程序既不終止也不呼叫 wait 清理子程序:

 

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    int i = 100;
    pid_t pid=fork();
    if(pid < 0)
    {
        perror("fork failed.");
        exit(1);
    }
    if(pid > 0)
    {
        printf("This is the parent process. My PID is %d.\n", getpid());
        for(; i > 0; i--)
        {
            sleep(1);
        }
    }
    else if(pid == 0)
    {
        printf("This is the child process. My PID is: %d. My PPID is: %d.\n", getpid(), getppid());
    }
    return 0;
}

 

把上面的程式碼儲存到檔案 zomprocdemo.c 檔案中,並執行下面的命令編譯:

$ gcc zomprocdemo.c -o zomprocdemo

然後執行編譯出來的 zomprocdemo 程式:

$ ./zomprocdemo

此時子程序已經退出,但是父程序沒有退出也沒有通過 wait() 呼叫處理子程序。我們使用 ps 命令檢視程序的狀態:

上圖紅框中的大寫字母 "Z" 說明 PID 為 112712 的程序此時處於僵死的狀態。

讓我們接著往下看!在結束 sleep 後父程序退出。當父程序退出後,子程序會變成孤兒程序,此時它會被一個管理程序收養。在不同的系統中,這個管理程序不太一樣,早期一般是 init 程序,Ubuntu 上是 upstart,還有近來的 Systemd。但是它們都完成相同的任務,就是 wiat() 這些孤兒程序,並最終釋放它們佔用的系統程序表中的資源。這樣,這些已經僵死的孤兒程序就徹底的被清除了。

殭屍程序的危害

在程序退出的時候,核心釋放該程序所有的資源,包括開啟的檔案,佔用的記憶體等。但是仍然為其保留一定的資訊(包括程序號 PID,退出狀態 the termination status of the process,執行時間 the amount of CPU time taken by the process 等)。直到父程序通過 wait / waitpid 來取時才釋放。

如果程序不呼叫 wait / waitpid 的話, 那麼保留的那段資訊就不會釋放,其程序號就會一直被佔用,但是系統所能使用的程序號是有限的,如果大量的產生僵死程序,將因為沒有可用的程序號而導致系統不能產生新的程序。

如何處理殭屍程序

殭屍程序的產生是因為父程序沒有 wait() 子程序。所以如果我們自己寫程式的話一定要在父程序中通過 wait() 來避免殭屍程序的產生。

當系統中出現了殭屍程序時,我們是無法通過 kill 命令把它清除掉的。但是我們可以殺死它的父程序,讓它變成孤兒程序,並進一步被系統中管理孤兒程序的程序收養並清理。

下面的 demo 中,父程序通過 wait() 等待子程序結束:

 

#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    pid_t pid;
    pid = fork();
    if (pid < 0)
    {
        perror("fork failed");
        exit(1);
    }
    if (pid == 0) {
        int i;
         for (i = 3; i > 0; i--)
         {
            printf("This is the child\n");
            sleep(1);
         }
         // exit with code 3 for test.
        exit(3);
    }
    else
    {
        int stat_val;
        wait(&stat_val);
         if (WIFEXITED(stat_val))
         {
             printf("Child exited with code %d\n", WEXITSTATUS(stat_val));
         }            
    }
    return 0;
}

 

demo 中父程序不僅等待子程序結束,還通過 WEXITSTATUS 巨集取到了子程序的 exit code。