精品国产人成在线_亚洲高清无码在线观看_国产在线视频国产永久2021_国产AV综合第一页一个的一区免费影院黑人_最近中文字幕MV高清在线视频

0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

linux下開發避免僵尸進程的方法

科技綠洲 ? 來源:Linux開發架構之路 ? 作者:Linux開發架構之路 ? 2023-11-11 16:38 ? 次閱讀

一、什么是僵死進程?

一般情況下,程序調用exit(包括_exit和_Exit,它們的區別這里不做解釋),它的絕大多數內存和相關的資源已經被內核釋放掉,但是在進程表中這個進程項(entry)還保留著(進程ID,退出狀態,占用的資源等等),你可能會問,為什么這么麻煩,直接釋放完資源不就行了嗎?這是因為有時它的父進程想了解它的退出狀態。在子進程退出但還未被其父進程“收尸”之前,該子進程就是僵死進程,或者僵尸進程。如果父進程先于子進程去世,那么子進程將被init進程收養,這個時候init就是這個子進程的父進程。

所以一旦出現父進程長期運行,而又沒有顯示調用wait或者waitpid,同時也沒有處理SIGCHLD信號,這個時候init進程就沒有辦法來替子進程收尸,這個時候,子進程就真的成了“僵尸”了。

二、僵死進程與孤兒進程的區別?

回答這個問題很簡單,就是爸爸(父進程)和兒子(子進程)誰先死的問題!

如果當兒子還在世的時候,爸爸去世了,那么兒子就成孤兒了,這個時候兒子就會被init收養,換句話說,init進程充當了兒子的爸爸,所以等到兒子去世的時候,就由init進程來為其收尸。

如果當爸爸還活著的時候,兒子死了,這個時候如果爸爸不給兒子收尸,那么兒子就會變成僵尸進程。

三、僵死進程的危害?

  1. 僵死進程的PID還占據著,意味著海量的子進程會占據滿進程表項,會使后來的進程無法fork.
  2. 僵死進程的內核棧無法被釋放掉(1K 或者 2K大小),為啥會留著它的內核棧,因為在棧的最低端,有著thread_info結構,它包含著 struct_task 結構,這里面包含著一些退出信息

四、避免僵死進程的方法

網上搜了下,總結有三種方方法:

① 程序中顯示的調用signal(SIGCHLD, SIG_IGN)來忽略SIGCHLD信號,這樣子進程結束后,由內核來wai和釋放資源

② fork兩次,第一次fork的子進程在fork完成后直接退出,這樣第二次fork得到的子進程就沒有爸爸了,它會自動被老祖宗init收養,init會負責釋放它的資源,這樣就不會有“僵尸”產生了

③ 對子進程進行wait,釋放它們的資源,但是父進程一般沒工夫在那里守著,等著子進程的退出,所以,一般使用信號的方式來處理,在收到SIGCHLD信號的時候,在信號處理函數中調用wait操作來釋放他們的資源。

五、對每個避免僵死進程方法的解析與總結

首先我們讓我們來看一個生成僵尸進程的程序zombie.c如下:

#include < stdio.h >  
#include < stdlib.h >  
#include < unistd.h >  
  
int main(int argc, const char *argv[])  
{  
    int i;  
    pid_t pid;  
  
    for (i = 0; i < 10; i++) {  
        if ((pid = fork()) == 0)    /* child */  
            _exit(0);  
    }  
    sleep(10);  
  
    exit(EXIT_SUCCESS);  
}

運行程序,在10s睡眠期間使用ps查看進程,你會發現有10個標記為“defunct”的僵尸進程:

圖片

接下來看第一種方法,程序avoid_zombie1.c如下:

#include < stdio.h >  
#include < stdlib.h >  
#include < signal.h >  
#include < unistd.h >  
#include < errno.h >  
  
int main(int argc, const char *argv[])  
{  
    pid_t pid;  
  
    if (SIG_ERR == signal(SIGCHLD, SIG_IGN)) {  
        perror("signal error");  
        _exit(EXIT_FAILURE);  
    }  
  
    while (1) {  
        if ((pid = fork()) == 0)    /* child */  
            _exit(0);  
    }  
  
    exit(EXIT_SUCCESS);  
}

程序運行期間通過ps命令的確沒有發現僵尸進程的存在。

在man文檔中有這段話:

Note that even though the default disposition of SIGCHLD is "ignore", explicitly setting the disposition to SIG_IGN results in different treatment of zombie process children.

意思是說盡管系統對信號SIGCHLD的默認處理就是“ignore”,但是顯示的設置成SIG_IGN的處理方式在在這里會表現不同的處理方式(即子進程結束后,資源由系統自動收回,所以不會產生僵尸進程),這是信號SIGCHLD與其他信號的不同之處。

在man文檔中同樣有這樣一段話:

The original POSIX standard left the behavior of setting SIGCHLD to SIG_IGN unspecified. 看來這個方法不是每個平臺都使用,尤其在一些老的系統中,兼容性不是很好,所以如果你在寫一個可移植的程序的話,不推薦使用這個方法。

第二種方法,即通過兩次fork來避免僵尸進程,我們來看一個例子avoid_zombie2.c:

#include < stdio.h >  
#include < stdlib.h >  
#include < signal.h >  
#include < unistd.h >  
#include < errno.h >  
  
int main(int argc, const char *argv[])  
{  
    pid_t pid;  
  
    while (1) {  
        if ((pid = fork()) == 0) {  /* child */  
            if ((pid = fork()) > 0)  
                _exit(0);  
            sleep(1);  
            printf("grandchild, parent id = %ldn",  
                            (long)getppid());  
            _exit(0);  
        }  
        if (waitpid(-1, NULL, 0) != pid) {  
            perror("waitpid error");  
            _exit(EXIT_FAILURE);  
        }  
    }  
  
    exit(EXIT_SUCCESS);  
}

這的確是個有效的辦法,但是我想這個方法不適宜網絡并發服務器中,應為fork的效率是不高的。

最后來看第三種方法, 也是最通用的方法

先看我們的測試程序avoid_zombie3.c

#include < stdio.h >  
#include < stdlib.h >  
#include < errno.h >  
#include < string.h >  
#include < libgen.h >  
#include < signal.h >  
#include < unistd.h >  
#include < sys/wait.h >  
#include < sys/types.h >  
  
  
void avoid_zombies_handler(int signo)  
{  
    pid_t pid;  
    int exit_status;  
    int saved_errno = errno;  
  
    while ((pid = waitpid(-1, &exit_status, WNOHANG)) > 0) {  
        /* do nothing */  
    }  
  
    errno = saved_errno;  
}  
  
int main(int argc, char *argv[])  
{  
    pid_t pid;  
    int status;  
    struct sigaction child_act;   
  
    memset(&child_act, 0, sizeof(struct sigaction));  
    child_act.sa_handler = avoid_zombies_handler;  
    child_act.sa_flags = SA_RESTART | SA_NOCLDSTOP;   
    sigemptyset(&child_act.sa_mask);  
    if (sigaction(SIGCHLD, &child_act, NULL) == -1) {  
        perror("sigaction error");  
        _exit(EXIT_FAILURE);  
    }  
  
    while (1) {  
        if ((pid = fork()) == 0) {  /* child process */  
            _exit(0);  
        } else if (pid > 0) {        /* parent process */  
        }  
    }  
      
    _exit(EXIT_SUCCESS);  
}

首先需要知道三點:

  1. 當某個信號的信號處理函數被調用時,該信號會被操作系統阻塞(默認sa_flags不設置SA_NODEFER標志)。

2.當某個信號的信號處理函數被調用時,該信號阻塞時,該信號又多次發生,那么操作系統并不將它們排隊,而是只保留第一次的,后續的被拋棄。

還有一點我們必須清楚的是

  1. wait系列函數與信號SIGCHLD是沒有任何關系的,即wait系列函數并不是信號SIGCHLD驅動的。

這個時候,肯定有人有疑問了,既然會丟棄信號,那怎么保證可以收回所有的僵尸進程呢?

關于這個問題,我們可以這樣來理解,當子進程結束時,不管有沒有產生SIGCHLD信號,或者子進程產生了SIGCHLD信號,而不管父進程有沒有收到SIGCHLD信號,這都與子進程已經終止這個事實無關,就是說,子進程終止與信號其實沒有任何關系,只是操作系統在子進程終止時會發送信號SIGCHLD給父進程,告之其子進程終止的消息,這樣的話,父進程就可以做相應的操作了。而wait系列函數的目的就是收回子進程終止時殘留在進程列表中的信息,所以任何時候調用while ((pid = waitpid(-1, &exit_status, WNOHANG)) > 0)都可以收回所有的僵尸進程信息(可以參考下面的程序)。但是這里為什么放在信號處理函數中處理了,這樣做的原因是:子進程什么時候結束是個異步事件,而信號機制就是用來處理異步事件的,所以當子進程結束時,可以迅速的收回其殘余信息,這樣系統中就不會積累大量的僵尸進程了。

也可以這樣來理解:系統把所有的僵尸進程串在一起形成一個僵尸進程鏈表,而while ((pid = waitpid(-1, &exit_status, WNOHANG)) > 0)就是來清空這個鏈表的,直到waitpid()返回0,表明已經沒有僵尸進程了,或者返回-1,表明出錯(當錯誤碼errno為ECHILD的時候同樣表明已經不存在僵尸進程了)。

了解了以上知識點,就能理解為什么while ((pid = waitpid(-1, &exit_status, WNOHANG)) > 0)能夠回收所有的僵尸進程了。

我們可以在上面的信號處理函數中加入相應的打印信息:

static int num1 = 0  
static int num2 = 0;  
void avoid_zombies_handler(int signo)  
{  
    pid_t pid;  
    int exit_status;  
    int saved_errno = errno;  
  
    printf("num1 = %dn", ++num1);  
    while ((pid = waitpid(-1, &exit_status, WNOHANG)) > 0) {  
        printf("num2 = %dn", ++num2);  
    }  
  
    errno = saved_errno;  
}

打印的結果你會發現,當num1遞增1的時候,即每調用一次信號處理函數,num2一般會遞增很多,即while循環了很多次,所以盡管有的SIGCHLD信號被丟棄了,但是我們不用擔心子進程的殘余信息會收不回來。退出while循環時,證明此時系統中已經沒有僵尸進程了,所以退出信號處理函數后,阻塞的唯一SIGCHLD信號會再次觸發該信號處理函數,這樣我們就不用擔心了。我們不防做個最壞的打算,即之前的信號全部被丟棄了,只有最后一次的SIGCHLD信號被捕獲,從而觸發了信號處理函數,這樣我們也不用擔心,因為while循環會一次性收回全部的僵尸進程信息,只是這次循環的次數要多得多罷了,當然這只是假設,一般系統不會出現這樣的情況(可以參考本文最后一個程序事例)。

為了證明wait系統函數與信號SIGCHLD沒有任何關系,我們可以做個簡單的實驗,代碼如下:

#include < stdio.h >  
#include < stdlib.h >  
#include < unistd.h >  
#include < sys/wait.h >  
#include < sys/types.h >  
  
int main(int argc, char *argv[])  
{  
    int i;  
    pid_t pid;  
  
    for (i = 0; i < 5; i++) {  
        if ((pid = fork()) == 0)    /* child */  
            _exit(0);  
    }  
    sleep(10);  
    while (waitpid(-1, NULL, WNOHANG) > 0) {  
        /* do nothing */  
    }  
    sleep(10);  
  
    _exit(EXIT_SUCCESS);  
}

以下是打印結果:

圖片

可以看到第一次sleep時系統中積累了5個僵尸進程,第二次sleep時,那5個僵尸進程都被收回了。這個也明顯的看到了使用信號處理函數的優勢,即可以保證系統不會積累大量的僵尸進程,它可以迅速的清理掉系統中的僵尸進程。

聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • Linux
    +關注

    關注

    87

    文章

    11123

    瀏覽量

    207908
  • 內存
    +關注

    關注

    8

    文章

    2903

    瀏覽量

    73536
  • 程序
    +關注

    關注

    115

    文章

    3720

    瀏覽量

    80357
  • WAIT
    +關注

    關注

    0

    文章

    4

    瀏覽量

    2502
收藏 人收藏

    評論

    相關推薦

    Linux開發_Linux進程編程

    介紹Linux進程概念、進程信號捕獲、進程管理相關的命令的使用等知識點。
    的頭像 發表于 09-17 15:38 ?1254次閱讀
    <b class='flag-5'>Linux</b><b class='flag-5'>開發</b>_<b class='flag-5'>Linux</b><b class='flag-5'>下</b><b class='flag-5'>進程</b>編程

    Linux系統進程的幾種狀態介紹

    文章對 Linux 系統進程的幾種狀態進行介紹,并對系統出現大量僵尸進程和不可中斷進程的場景進
    發表于 11-24 16:15 ?1.2w次閱讀
    <b class='flag-5'>Linux</b>系統<b class='flag-5'>下</b><b class='flag-5'>進程</b>的幾種狀態介紹

    Linux進程間如何實現共享內存通信

    這次我們來講一Linux進程通信中重要的通信方式:共享內存作為Linux軟件開發攻城獅,進程
    發表于 04-26 17:14 ?639次閱讀

    linux查詢進程占用的內存方法有哪些?

    linux查詢進程占用的內存方法
    發表于 04-08 06:03

    孤兒進程僵尸進程

    前段時間,由于研究經典面試題,把孤兒進程僵尸進程也總結了一。我們有這樣一個問題:孤兒進程僵尸
    發表于 11-29 14:08

    Linux進程結構

    (TASK_KILLABLE):Linux內核 2.6.25 引入了一種新的進程狀態,名為 TASK_KILLABLE。該狀態的運行機制類似于 TASK_UNINTERRUPTIBLE,只不過處在該狀態
    發表于 05-27 09:24

    為什么會出現LINUX僵尸進程

    僵尸進程出現在父進程沒有回收子進程的PCB的時候,這個時候子進程已經結束,但是父進程沒有回收他,
    發表于 08-07 06:48

    進程有幾種狀態?

    ?線程間同步方法有哪些?什么是內核線程和用戶線程?內核線程和用戶線程的區別?內核線程和用戶線程有什么優缺點?什么是僵尸進程,孤兒進程,守護進程
    發表于 12-24 07:16

    僵尸進程的產生介紹和危害以及解決方法

    如果你經常使用 Linux,你應該遇到這個術語“僵尸進程Zombie Processes”。 那么什么是僵尸進程? 它們是怎么產生的? 它們
    的頭像 發表于 12-18 15:56 ?5671次閱讀
    <b class='flag-5'>僵尸</b><b class='flag-5'>進程</b>的產生介紹和危害以及解決<b class='flag-5'>方法</b>

    什么是僵尸進程_Linux僵尸進程可以被“殺死”嗎?

    首先要明確一點,僵尸進程的含義是:子進程已經死了,但是父進程還沒有wait它的一個中間狀態,這個時候子進程是一個
    的頭像 發表于 07-28 10:09 ?4599次閱讀
    什么是<b class='flag-5'>僵尸</b><b class='flag-5'>進程</b>_<b class='flag-5'>Linux</b><b class='flag-5'>僵尸</b><b class='flag-5'>進程</b>可以被“殺死”嗎?

    Linux 系統中僵尸進程

    不合理,父進程從不調用 wait 等系統調用來收集僵尸進程,那么這些進程會一直存在內存中。在 Linux
    發表于 04-02 14:40 ?412次閱讀

    Linux數據中心服務器上的僵尸進程怎樣正確的處理

    。雖然僵尸進程不像運行中的流氓應用程序那樣占用寶貴資源,但可能會構成威脅。
    發表于 09-30 17:29 ?839次閱讀
    <b class='flag-5'>Linux</b>數據中心服務器上的<b class='flag-5'>僵尸</b><b class='flag-5'>進程</b>怎樣正確的處理

    Linux僵尸進程會被殺死嗎?

    那么,根據POSIX標準關于信號(signal)的定義,當我們執行kill -9 4730(4730是4730和4731的TGID,也是整個進程用戶態視角的PID)的時候,是要殺死整個4730進程的,所以這個時候4731被我們殺死,整個
    發表于 08-07 16:48 ?332次閱讀
    <b class='flag-5'>Linux</b><b class='flag-5'>僵尸</b><b class='flag-5'>進程</b>會被殺死嗎?

    如何在Linux終止僵尸進程

    在了解Zombie進程之前,讓我回憶一什么是進程。簡而言之,進程是程序實例。它可以是前臺的交互式進程或后臺的非交互式或自動
    的頭像 發表于 12-12 17:40 ?1973次閱讀

    如何查看系統是否有僵尸進程

    進程中的指令已經執行完成,但是進程PCB結構還沒有回收。   即子進程先于父進程退出后,子進程的PCB需要其父
    的頭像 發表于 11-29 15:52 ?5764次閱讀
    如何查看系統是否有<b class='flag-5'>僵尸</b><b class='flag-5'>進程</b>