1. 程式人生 > >linux處理僵屍進程

linux處理僵屍進程

階段 返回值 相等 main 銷毀 控制 spa class font

由來

在linux下,如果一個進程終止,內核會釋放該進程使用的所有存儲區,關閉所有文件句柄等,但是,內核會為每個終止子進程保留一定量的信息。這些信息至少包括進程ID,進程的終止狀態,以及該進程使用的CPU時間。當終止子進程的父進程調用wait或waitpid時就可以得到這些信息

僵屍進程指:一個進程退出後,而其父進程並沒有為它收屍(調用wait或waitpid來獲得它的結束狀態)的進程

任何一個子進程(init除外)在退出後並非馬上就消失,而是留下一個稱為僵屍進程的數據結構,等待父進程處理。這是每個子進程都必需經歷的階段。另外子進程退出的時候會向其父進程發送一個SIGCHLD信號

作用

設置僵死狀態的目的是維護子進程的信息,以便父進程在以後某個時候獲取。如果一個進程終止,而該進程有子進程處於僵屍狀態,那麽它的所有僵屍子進程的父進程ID將被重置為1(init進程)。init進程將清理它們(也就是說init進程將wait它們,從而去除它們的僵屍狀態)

危害

內核保存僵屍進程信息,這就是占用了系統資源,而且僵屍進程的進程號一直會被占用,但是系統所能使用的進程號是有限的,如果大量的產生僵死進程,將因為沒有可用的進程號而導致系統不能產生新的進程

避免

1. 通過signal(SIGCHLD, SIG_IGN)通知內核對子進程的結束不關心,由內核回收。

2. 父進程調用wait/waitpid等函數等待子進程結束,如果尚無子進程退出,wait會導致父進程阻塞。waitpid可以通過傳遞WNOHANG使父進程不阻塞立即返回。

3. 如果父進程很忙可以用signal註冊信號處理函數,在信號處理函數調用wait/waitpid等待子進程退出

4. 通過兩次調用fork。父進程首先調用fork創建一個子進程然後waitpid等待子進程退出,子進程再fork一個孫進程後退出。這樣子進程退出後會被父進程等待回收,而對於孫子進程其父進程已經退出所以孫進程成為一個孤兒進程,孤兒進程由init進程接管,孫進程結束後,init會等待回收。

第一種方法忽略SIGCHLD信號,這常用於並發服務器的性能的一個技巧因為並發服務器常常fork很多子進程,子進程終結之後需要服務器進程去wait清理資源。如果將此信號的處理方式設為忽略,可讓內核把僵屍子進程轉交給init進程去處理,省去了大量僵屍進程占用系統資源。

wait函數

pid_t wait(int *status);

進程一旦調用了wait,就立即阻塞自己,由wait自動分析是否當前進程的某個子進程已經退出,如果讓它找到了這樣一個已經變成僵屍的子進程,wait就會收集這個子進程的信息,並把它徹底銷毀後返回;如果沒有找到這樣一個子進程,wait就會一直阻塞在這裏,直到有一個出現為止。


參數status用來保存被收集進程退出時的一些狀態,它是一個指向int類型的指針。但如果我們對這個子進程是如何死掉的毫不在意,只想把這個僵屍進程消滅掉,(事實上絕大多數情況下,我們都會這樣想),我們就可以設定這個參數為NULL

如果成功,wait會返回被收集的子進程的進程ID,如果調用進程沒有子進程,調用就會失敗,此時wait返回-1,同時errno被置為ECHILD。

  • wait系統調用會使父進程暫停執行,直到它的一個子進程結束為止。
  • 返回的是子進程的PID,它通常是結束的子進程
  • 狀態信息允許父進程判定子進程的退出狀態,即從子進程的main函數返回的值或子進程中exit語句的退出碼。
  • 如果status不是一個空指針,狀態信息將被寫入它指向的位置

可以上述的一些宏判斷子進程的退出情況:

技術分享圖片

waitpid函數

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

status:如果不是空,會把狀態信息寫到它指向的位置,與wait一樣

options:允許改變waitpid的行為,最有用的一個選項是WNOHANG,它的作用是防止waitpid把調用者的執行掛起

The value of options is an OR of zero or more of the following con-
stants:

WNOHANG return immediately if no child has exited.

WUNTRACED also return if a child has stopped (but not traced via
ptrace(2)). Status for traced children which have stopped
is provided even if this option is not specified.

WCONTINUED (since Linux 2.6.10)
also return if a stopped child has been resumed by delivery
of SIGCONT.

返回值:如果成功返回等待子進程的ID,失敗返回-1

對於waitpid的p i d參數的解釋與其值有關:

pid == -1 等待任一子進程。於是在這一功能方面waitpid與wait等效。

pid > 0 等待其進程I D與p i d相等的子進程。

pid == 0 等待其組I D等於調用進程的組I D的任一子進程。換句話說是與調用者進程同在一個組的進程。

pid < -1 等待其組I D等於p i d的絕對值的任一子進程

wait與waitpid區別:

  • 在一個子進程終止前, wait 使其調用者阻塞,而waitpid 有一選擇項,可使調用者不阻塞。
  • waitpid並不等待第一個終止的子進程—它有若幹個選擇項,可以控制它所等待的特定進程。
  • 實際上wait函數是waitpid函數的一個特例。waitpid(-1, &status, 0);

如果使用wait處理僵屍進程,如下程序:

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


int cnt = 0;

void waitpidexit(int sig)
{
    while (waitpid(-1, NULL, WNOHANG) > 0)
    {
        cnt++;
    }
}

void waitexit(int sig)
{
    wait(NULL);
    cnt++;
}

int main()
{
    int pid;

    printf("%d\n", getpid());

    // signal(SIGCHLD, SIG_IGN);
    signal(SIGCHLD, waitexit);
    // signal(SIGCHLD, waitpidexit);

    for (int i = 0; i < 500; ++i)
    {
        pid = fork();
        if (pid == 0 || pid < 0)
        {
            break;
        }
    }

    if (pid == 0)
    {
        exit(0);
    }
    else if (pid < 0)
    {
        fprintf(stderr, "fork error: %d, %s\n", errno, strerror(errno));
        exit(0);
    }
    else
    {
        getchar();

        printf("%d\n", cnt);
    }

    return 0;
}

運行之後,發現還是有很多僵屍進程:

技術分享圖片

這是因為 SIGCHLD 為不可靠非實時信號,不支持排隊,可能會造成信號丟失。假如在某一時間段多個子進程退出後都會發出SIGCHLD信號,但父進程來不及一個一個地響應,所以最後父進程實際上只執行了一次信號處理函數。但執行一次信號處理函數只等待一個子進程退出,所以最後會有一些子進程依然是僵屍進程。

雖然這樣但是有一點是明了的,就是收到SIGCHLD必然有子進程退出,而我們可以在信號處理函數裏循環調用waitpid函數來等待所有的退出的子進程。至於為什麽不用wait,主要原因是在wait在清理完所有僵屍進程後再次等待會阻塞。

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


int cnt = 0;

void waitpidexit(int sig)
{
    while (waitpid(-1, NULL, WNOHANG) > 0)
    {
        cnt++;
    }
}

void waitexit(int sig)
{
    wait(NULL);
    cnt++;
}

int main()
{
    int pid;

    printf("%d\n", getpid());

    // signal(SIGCHLD, SIG_IGN);
    // signal(SIGCHLD, waitexit);
    signal(SIGCHLD, waitpidexit);

    for (int i = 0; i < 500; ++i)
    {
        pid = fork();
        if (pid == 0 || pid < 0)
        {
            break;
        }
    }

    if (pid == 0)
    {
        exit(0);
    }
    else if (pid < 0)
    {
        fprintf(stderr, "fork error: %d, %s\n", errno, strerror(errno));
        exit(0);
    }
    else
    {
        getchar();

        printf("%d\n", cnt);
    }

    return 0;
}

linux處理僵屍進程