搜尋
首頁php教程PHP开发linux殭屍行程產生的原因以及如何避免產生殭屍行程

給進程設定殭屍狀態的目的是維護子進程的訊息,以便父​​進程在以後某個時間取得。這些資訊包括子進程的進程ID、終止狀態以及資源利用資訊(CPU時間,記憶體使用量等等)。如果一個行程終止,而該行程有子行程處於殭屍狀態,那麼它的所有殭屍子程序的父行程ID將被重設為1(init行程)。繼承這些子程序的init程序將清理它們(init程序將wait它們,從而去除殭屍狀態)。

        但通常情況下,我們是不願意留存殭屍進程的,它們佔用核心中的空間,最終可能導致我們耗盡進程資源。那麼為什麼會產生殭屍行程以及如何避免產生殭屍行程呢?下邊我將從這兩個方面來分析。

    殭屍進程的原因

        我們知道,在目前進程中產生一個子進程,一般需要調用fork這個系統調用,fork這個函數的特別之處在於一次調用,兩次返回之處在於一次返回到父進程中,一次回到子進程中,我們可以透過回傳值來判斷其回傳點:

pid_t child = fork();if( child < 0  ) {     //fork error.
    perror("fork process fail.\n");
} else if( child ==0  ) {   // in child process
    printf(" fork succ, this run in child process\n ");
} else {                        // in parent process
    printf(" this run in parent process\n ");
}

       如果子程序先於父程序退出, 同時父程序又沒有呼叫wait/waitpid,則該子程序將成為殭屍程序。透過ps指令,我們可以看到這個行程的狀態為Z(表示僵死),如圖1所示:

 linux僵尸进程

            (圖1)

    備註指令defunct>指明殭屍行程。

        代碼如下:

if( child == -1 ) { //error
    perror("\nfork child error.");
    exit(0);
} else if(child == 0){
   cout << "\nIm in child process:" <<  getpid() << endl;
   exit(0);
} else {
   cout << "\nIm in parent process."  << endl;
   sleep(600);
}

        讓父進程休眠600s, 然後子程序先退出,我們就可以看到殭屍   我們知道了殭屍行程產生的原因,下邊我們看看如何避免產生殭屍行程。

        一般,為了防止產生殭屍進程,在fork子進程之後我們都要wait它們;同時,當子進程退出的時候,核心都會給父進程一個SIGCHLD信號,所以我們可以建立一個捕獲SIGCHLD信號的信號處理函數,在函數體中呼叫wait(或waitpid),就可以清理退出的子程序以達到防止殭屍程序的目的。如下程式碼所示:

void sig_chld( int signo ) {
    pid_t pid;    int stat;
    pid = wait(&stat);    
    printf( "child %d exit\n", pid );    return;
}int main() {
    signal(SIGCHLD,  &sig_chld);
}

       現在main函數中給SIGCHLD訊號註冊一個訊號處理函數(sig_chld),然後在子行程退出的時候,核心遞交一個SIGCHLD的時候就會被主行程擷取而進入訊號處理函數sig_chld ,然後再在sig_chld中呼叫wait,就可以清理退出的子程序。這樣退出的子行程就不會成為殭屍行程。

        然後,即使我們捕獲SIGCHLD訊號並且呼叫wait來清理退出的進程,仍然不能徹底避免產生殭屍進程;我們來看一種特殊的情況:

     連結過來的client,server都啟動一個新的程序去處理來自這個client的請求。然後我們有一個client進程,在這個進程內,發起了多個到server的請求(假設5個),則server會fork 5個子程序來讀取client輸入並處理(同時,當客戶端關閉套接字的時候,每個子程序都退出);當我們終止這個client進程的時候,核心將自動關閉所有由這個client進程打開的套接字,那麼由這個client進程發起的5個連接基本在同一時刻終止。這就引發了5個FIN,每個連接一個。 server端接受到這5個FIN的時候,5個子進程基本上在同一時刻終止。這又導致差不多在同一時刻遞交5個SIGCHLD訊號給父進程,如圖2所示:

          (圖2)

 linux僵尸进程         (圖2)

      的問題。

        我們先執行伺服器程序,然後執行客戶端程序,並使用ps指令看以看到伺服器fork了5個子進程,如圖3:

                    (图3)

        然后我们Ctrl+C终止客户端进程,在我机器上边测试,可以看到信号处理函数运行了3次,还剩下2个僵尸进程,如图4:

 linux僵尸进程

(图4)

       通过上边这个实验我们可以看出,建立信号处理函数并在其中调用wait并不足以防止出现僵尸进程,其原因在于:所有5个信号都在信号处理函数执行之前产生,而信号处理函数只执行一次,因为Unix信号一般是不排队的。

更为严重的是,本问题是不确定的,依赖于客户FIN到达服务器主机的时机,信号处理函数执行的次数并不确定。

       正确的解决办法是调用waitpid而不是wait,这个办法的方法为:信号处理函数中,在一个循环内调用waitpid,以获取所有已终止子进程的状态。我们必须指定WNOHANG选项,他告知waitpid在有尚未终止的子进程在运行时不要阻塞。(我们不能在循环内调用wait,因为没有办法防止wait在尚有未终止的子进程在运行时阻塞,wait将会阻塞到现有的子进程中第一个终止为止),下边的程序分别给出了这两种处理办法(func_wait, func_waitpid)。

//server.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <errno.h>
#include <error.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <string.h>
#include <signal.h>
#include <sys/wait.h>
typedef void sigfunc(int);
void func_wait(int signo) {
    pid_t pid;
    int stat;
    pid = wait(&stat);    
    printf( "child %d exit\n", pid );
    return;
}
void func_waitpid(int signo) {
    pid_t pid;
    int stat;
    while( (pid = waitpid(-1, &stat, WNOHANG)) > 0 ) {
        printf( "child %d exit\n", pid );
    }
    return;
}
sigfunc* signal( int signo, sigfunc *func ) {
    struct sigaction act, oact;
    act.sa_handler = func;
    sigemptyset(&act.sa_mask);
    act.sa_flags = 0;
    if ( signo == SIGALRM ) {
#ifdef            SA_INTERRUPT
        act.sa_flags |= SA_INTERRUPT;    /* SunOS 4.x */
#endif
    } else {
#ifdef           SA_RESTART
        act.sa_flags |= SA_RESTART;    /* SVR4, 4.4BSD */
#endif
    }
    if ( sigaction(signo, &act, &oact) < 0 ) {
        return SIG_ERR;
    }
    return oact.sa_handler;
} 
void str_echo( int cfd ) {
    ssize_t n;
    char buf[1024];
again:
    memset(buf, 0, sizeof(buf));
    while( (n = read(cfd, buf, 1024)) > 0 ) {
        write(cfd, buf, n); 
    }
    if( n <0 && errno == EINTR ) {
        goto again; 
    } else {
        printf("str_echo: read error\n");
    }
}
int main() {
    signal(SIGCHLD, &func_waitpid);    
    int s, c;
    pid_t child;
    if( (s = socket(AF_INET, SOCK_STREAM, 0)) < 0 ) {
        int e = errno; 
        perror("create socket fail.\n");
        exit(0);
    }
    
    struct sockaddr_in server_addr, child_addr; 
    bzero(&server_addr, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(9998);
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    if( bind(s, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0 ) {
        int e = errno; 
        perror("bind address fail.\n");
        exit(0);
    }
    
    if( listen(s, 1024) < 0 ) {
        int e = errno; 
        perror("listen fail.\n");
        exit(0);
    }
    while(1) {
        socklen_t chilen = sizeof(child_addr); 
        if ( (c = accept(s, (struct sockaddr *)&child_addr, &chilen)) < 0 ) {
            perror("listen fail.");
            exit(0);
        }
        if( (child = fork()) == 0 ) {
            close(s); 
            str_echo(c);
            exit(0);
        }
        close(c);
    }
}
//client.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <errno.h>
#include <error.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <string.h>
#include <signal.h>
void str_cli(FILE *fp, int sfd ) {
    char sendline[1024], recvline[2014];
    memset(recvline, 0, sizeof(sendline));
    memset(sendline, 0, sizeof(recvline));
    while( fgets(sendline, 1024, fp) != NULL ) {
        write(sfd, sendline, strlen(sendline)); 
        if( read(sfd, recvline, 1024) == 0 ) {
            printf("server term prematurely.\n"); 
        }
        fputs(recvline, stdout);
        memset(recvline, 0, sizeof(sendline));
        memset(sendline, 0, sizeof(recvline));
    }
}
int main() {
    int s[5]; 
    for (int i=0; i<5; i++) {
        if( (s[i] = socket(AF_INET, SOCK_STREAM, 0)) < 0 ) {
            int e = errno; 
            perror("create socket fail.\n");
            exit(0);
        }
    }
    for (int i=0; i<5; i++) {
        struct sockaddr_in server_addr, child_addr; 
        bzero(&server_addr, sizeof(server_addr));
        server_addr.sin_family = AF_INET;
        server_addr.sin_port = htons(9998);
        inet_pton(AF_INET, "127.0.0.1", &server_addr.sin_addr);
        if( connect(s[i], (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0 ) {
            perror("connect fail."); 
            exit(0);
        }
    }
    sleep(10);
    str_cli(stdin, s[0]);
    exit(0);
}



更多 linux僵尸进程产生的原因以及如何避免产生僵尸进程相关文章请关注PHP中文网!

陳述
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

DVWA

DVWA

Damn Vulnerable Web App (DVWA) 是一個PHP/MySQL的Web應用程序,非常容易受到攻擊。它的主要目標是成為安全專業人員在合法環境中測試自己的技能和工具的輔助工具,幫助Web開發人員更好地理解保護網路應用程式的過程,並幫助教師/學生在課堂環境中教授/學習Web應用程式安全性。 DVWA的目標是透過簡單直接的介面練習一些最常見的Web漏洞,難度各不相同。請注意,該軟體中

MantisBT

MantisBT

Mantis是一個易於部署的基於Web的缺陷追蹤工具,用於幫助產品缺陷追蹤。它需要PHP、MySQL和一個Web伺服器。請查看我們的演示和託管服務。

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

mPDF

mPDF

mPDF是一個PHP庫,可以從UTF-8編碼的HTML產生PDF檔案。原作者Ian Back編寫mPDF以從他的網站上「即時」輸出PDF文件,並處理不同的語言。與原始腳本如HTML2FPDF相比,它的速度較慢,並且在使用Unicode字體時產生的檔案較大,但支援CSS樣式等,並進行了大量增強。支援幾乎所有語言,包括RTL(阿拉伯語和希伯來語)和CJK(中日韓)。支援嵌套的區塊級元素(如P、DIV),