天天看點

僵屍程序詳解

僵屍程序簡介

“僵屍”程序是什麼?通常情況下,造成僵屍程序的成因是因為該程序本應該已經執行完畢,但是該程序的父程序卻無法完整的将該程序結束掉,而造成該程序一直存在于記憶體中。

那麼如何檢視一個程序是否為僵屍程序呢?

ps:将某個時間點的程序運作狀态選取下來

ps aux    //檢視系統所有的程序資料
-A:所有的程序均顯示出來
-a:不與terminal有關的所有程序
-u:有效使用者相關的程序
-x:通常與a一起使用,可以列出較完整的資訊
-l:較長、較詳細地将該PID的資訊列出
           

如圖:

僵屍程式詳解

如上圖

1、 F:程序标志,說明這個程序的權限,常見号碼有:

若為4則代表權限為root

若為1則代表僅可被複制(fork),而無法實際執行(exec)

2、S:代表程序的狀态

R ( Running):正在運作的程序

S(Sleeping):正在睡眠的程序,但可被喚醒

D:不可被喚醒的睡眠狀态,一般都是在進行資料的I/O

T:停止狀态

Z(Zombie):僵屍狀态,程序已經終止但卻無法被删除至記憶體外

也可以通過top指令來檢視是否存在僵屍程序

top:動态檢視程序的變化

top:參數
-d:後面接秒數,表示顯示整個程序界面更新的秒數,如top -d 
-b:以批次的方式執行top
-n:與-b搭配,意思是需要幾次top輸出的結果
-p:指定某個特定的PID進行檢測
           

如圖:

僵屍程式詳解

光标處即目前的僵屍程序數量。

舉例

這是一個維持30秒的僵屍程序

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

int main(int argc,char** argv[])
{
    int id = fork();

    if(id>)
    {
        printf("Parent is sleeping\n");
        sleep();
    }
    if(id == )
    printf("Child process is done\n");

    exit(EXIT_SUCCESS);
}
           

通俗一點,僵屍程序就是指子程序先于父程序挂掉 但是父程序并沒有正确回收子程序的資源而已。

程序的管理

當你獲知它是一個僵屍程序後,那麼你該如何幹掉它呢,那麼首先就得了解一下程序的管理。

程式之間的互相管理,是通過給予一個信号來進行管理的

檢視信号(signal):

1、man 7 signal

2、kill -l

僵屍程式詳解

通常情況下,我們隻需記住幾個特别重要的信号即可。

1:啟動被終止的程序,可讓該PID重新讀取自己的配置檔案

9:強制中段一個程序,如果該程序運作到一半(如vim)會産生.filename.swap的半産品檔案

15:正常結束一個程序

18:繼續運作該程序

19:暫停一個程序

例如,強行殺掉一個程序:

僵屍程式詳解

在實際情況下,如果我們有時無法直接殺掉一個僵屍程序,可以找到其父程序将其殺掉,進而幹掉該僵屍程序。

總的來說,當系統不穩定時,或者代碼不夠完善,亦或是使用者操作不當都可能産生僵屍程序,而僵屍程序是1個早已死亡的程序,但在程序表(processs table)中仍占了1個位置(slot)。由于程序表的容量是有限的,是以就占用了記憶體資源,影響系統性能。

補充:

如何防止僵屍程序的産生

如上所述,當子程序先行退出,且父程序不對其做回收的話,會産生僵屍程序,我們可以通過的程序等待,來讓父程序等待子程序退出然後進行回收。

具體的程序等待這裡就不贅述了。可以參考程序控制中的程序等待部分。

這裡主要是為了提一下後續學到了信号部分的知識,當子程序退出時作業系統會發送SIGCHLD信号,而該信号的預設處理動作是忽略,父程序可以自定義SIGCHLD信号的處理函數,這樣父程序就可以處理自己的事情,而不必關心子程序的退出,在父程序的處理函數中調用wait清理子程序即可。

看如下代碼:

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

//自定義SIGCHLD信号的處理函數,對子程序程序wait
void Myhandler(sigset_t signal)
{
    printf("wait\n");
    wait(NULL);
}

int main()
{
    //當收到SIGCHLD信号時,執行自定義的處理函數
    signal(SIGCHLD,Myhandler);
    pid_t id = fork();
    if(id > ){
        //father
        while(){
            printf("father doing some thing!\n");
            sleep();
        }
    }else if(id == ){
        //child
        sleep();
        exit();
    }else{
        perror("fork");
        return ;
    }
    return ;
}
           

這段代碼子程序先退出,但是子程序退出後會給父程序發送一個SIGCHLD信号,在我們的程式中,當父程序收到一個SIGCHLD信号後,執行自定義的處理函數,在處理函數中進行wait。這樣就避免了僵屍程序的産生。

如下運作結果

僵屍程式詳解

但是如果建立20個子程序呢?還能避免所有的僵屍程序嘛?

看如下代碼:

void Myhandler(sigset_t signal)
{
    printf("wait\n");
    wait(NULL);
}

int main()
{
    signal(SIGCHLD,Myhandler);
    pid_t cid;
    int i = ;
    for(;i < ;++i)
    {
        cid = fork();
        if(cid == )
        exit();
    }
    if(cid > )
    {
        while()
        {
            printf("father doing some thing!\n");
            sleep();
        }
    }
    else if(cid == )
    {
        sleep();
    }
    return ;
}
           
僵屍程式詳解

可以看到,隻wait了15次,這樣意味着産生了5個僵屍程序,來檢視一下系統中是否真的出現了5個僵屍程序。

僵屍程式詳解

解釋下為什麼會出現僵屍程序,因為作業系統在接受到一個信号時,在執行處理函數時,就會屏蔽該信号,是以當有多個子程序同時退出發送信号時,作業系統就收到一個信号,是以就造成僵屍程序的出現。

基于這個問題,可以采用兩種發生來解決。

一:一次處理函數wait多個子程序

這裡用到waitpid這個函數,函數傳回值正常傳回子程序的PID,如果設定了第三個參數,即WNOHANG表示非阻塞式等待時,如果傳回值為0則表示沒有需要回收的子程序了,基于這一點,我們可以在處理函數中循環調用waitpid函數。

代碼如下:

void Myhandler(sigset_t signal)
{
    pid_t id;
    while((id = waitpid(-,NULL,WNOHANG) > )){
        printf("child wait success:%d\n",id);
    }
    printf("child is quit!\n");
}

int main()
{
    signal(SIGCHLD,Myhandler);
    pid_t cid;
    int i = ;
    for(;i < ;++i)
    {
        cid = fork();
        if(cid == )
        exit();
    }
    if(cid > )
    {
        while()
        {
            printf("father doing some thing!\n");
            sleep();
        }
    }
    else if(cid == )
    {
        sleep();
    }
    return ;
}
           
僵屍程式詳解
僵屍程式詳解

由于篇幅問題,有一點沒截上,可以看到子程序全部被回收了,系統中并沒有産生僵屍程序。

二:使用sigaction函數

sigaction是後來取代signal函數出現的,以為它的接口更豐富,還有另外一種方法就是使用sigaction将函數将SIGCHLD的預設處理函數設定為SIG_IGN,這樣fork出來的子程序在終止時會自己清理掉,不會産生僵屍程序,也不會通知父程序。

代碼如下:

int main()
{
    struct sigaction new,old;
    new.sa_handler = SIG_IGN;
    sigemptyset(&new.sa_mask);
    new.sa_flags = ;
    sigaction(SIGCHLD,&new,&old);
    pid_t cid;
    int i = ;
    for(;i < ;++i)
    {
        cid = fork();
        if(cid == )
        exit();
    }
    if(cid > )
    {
        while()
        {
            printf("father doing some thing!\n");
            sleep();
        }
    }
    else if(cid == )
    {
        sleep();
    }
    sigaction(SIGCHLD,&old,NULL);
    return ;
}
           

就先補充到這了,後續有新的了解,會繼續更新。

繼續閱讀