tcp回射服務器程序處理僵死進程

tcp回射服務器程序處理僵死進程

什麼是僵死進程

當子進程調用exit指令退出的時候,會留下一個一個成爲僵死(zombie)的數據結構,目的是維護子進程的信息,以便父進程在以後的某個時候獲取,這些信息包括子進程的進程ID,終止狀態以及資源利用信息等。在退出時他會向父進程發送SIGCHLD信號,如果父進程沒有對該信號進行處理,該退出的子進程就會一直處於僵死狀態,佔用內核中的空間,多了以後甚至會導致我們耗盡進程資源。

如果tcp回射服務器不對SIGCHLD信號進行處理

tcp回射服務器程序如下(摘錄自《UNIX網絡編程 卷一》)

#include    "unp.h"

int
main(int argc, char **argv)
{
    int                 listenfd, connfd;
    pid_t               childpid;
    socklen_t           clilen;
    struct sockaddr_in  cliaddr, servaddr;

    listenfd = Socket(AF_INET, SOCK_STREAM, 0);

    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family      = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port        = htons(SERV_PORT);

    Bind(listenfd, (SA *) &servaddr, sizeof(servaddr));

    Listen(listenfd, LISTENQ);

    for ( ; ; ) {
        clilen = sizeof(cliaddr);
        connfd = Accept(listenfd, (SA *) &cliaddr, &clilen);

        if ( (childpid = Fork()) == 0) {    /* child process */
            Close(listenfd);    /* close listening socket */
            str_echo(connfd);   /* process the request */
            exit(0);
        }
        Close(connfd);          /* parent closes connected socket */
    }
}

以上代碼段未對SIGCHLD信號進行處理,所以導致我們鍵入EOF字符來終止客戶時候,對應該套接字的子進程就會一直處於僵死狀態。
把兩個客戶端程序關閉,父進程沒有對子進程發來的SIGCHLD信號處理導致子進程一直處於僵死狀態

把兩個客戶端程序關閉,父進程沒有對子進程發來的SIGCHLD信號處理導致子進程一直處於僵死狀態。

對SIGCHLD信號進行處理

父進程接收到SIGCHLD信號後調用wait或waitpid函數來爲子進程”收屍”。這兩個函數的原型如下:

#include<sys/wait.h>
#include    "unp.h"
pid_t wait(int *statloc);
pid_t waitpid(pid_t pid,int *statloc,int options);
/*這兩個函數均返回兩個值:已終止的進程ID號和通過statloc指針返回的子進
程終止狀態。通過設置options,我們可以指定附加選項,常用的選項是
WNOHANG,它告知內核在沒有已終止子進程時不要阻塞。pid參數允許我們制定
想等待的進程ID,如果值爲-1則表示等待第一個終止的進程。
*/


/*
如果調用wait的進程沒有已終止的子進程,不過有一個或多個子進程仍在執行,
那麼wait將阻塞到現有的子進程第一個終止爲止
*/

void sig_chld(int signo) {
    pid_t pid;
    int stat;
    pid = wait(&stat);
    printf("chlild %d terminated\n",pid);
    return;
}

//如果調用waitpid則可以通過循環等待所有子進程結束
void sig_chld(int signo) {
    pid_t pid;
    int stat;
    while(pid = waitpid(-1,&stat,WNOHANG)) > 0)
        printf("child %d terminated\n",pid);
    return;
}

我們應該使用wait還是waitpid?答案肯定是waitpid。我們假設一個情景:客戶端程序通過循環與服務器建立了5個TCP連接,然後退出客戶端程序,這5個連接幾乎在同一時刻發給父進程SIGCHLD信號。因爲Unix信號一般是不排隊的,信號處理函數只執行一次。所以,如果使用wait,處理完第一個信號,信號處理函數就返回了,導致其他四個信號沒有得到處理,還是導致了僵死。如果用waitpid的時候,我們可以通過一個循環,處理所有SIGCHLD信號,這樣就沒有僵死信號存留了。
注意:我們應該在listen調用之後增加該信號處理函數
Signal(SIGCHLD,sig_chld);
這必須在fork第一個子進程之前完成,且只做一次)

編寫捕獲信號的網絡程序時,必須認清被中斷的系統調用並處理他們

我們的服務器程序阻塞於慢系統調用(accept)捕獲該信號,內核就會使accept返回一個EINTR錯誤(被中斷的系統調用)而如果父進程不處理該錯誤,就會被中止。對於那些可能永遠阻塞的函數,我們可以稱之爲慢系統調用。有些內核會自動重啓被中斷的系統調用,有些不會。所以爲了程序的健壯性。我們要做的事情就是自己重啓被中斷的系統調用。

//在判斷到EINTR錯誤的時候,執行continue返回循環重啓accept
//因爲本文的程序基本摘抄自《Unix網絡編程 卷一》所以讀者如過要
//編譯這些代碼的話需要該書提供的一些庫
for( ; ;) {
    clilen = sizeof(cliaddr);
    if( (connfd = accept(listenfd, (SA*) &cliaddr, &clilen) < 0) {
    if(errno == EINTR)
        continue;
    }
    else 
        err_sys("accept error");
}

總結

我們在網絡編程的時候要注意這三種情況:

  • 當fork子進程時,必須捕獲SIGCHLD信號
  • 當捕獲信號時,必須處理被中斷的系統調用
  • SIGCHLD的信號處理函數必須正確編寫,應使用waitpid函數以免下僵死進程。

    最後貼出注意以上三點後的服務器程序代碼

#include    "unp.h"

int
main(int argc, char **argv)
{
    int                 listenfd, connfd;
    pid_t               childpid;
    socklen_t           clilen;
    struct sockaddr_in  cliaddr, servaddr;
    void                sig_chld(int);

    listenfd = Socket(AF_INET, SOCK_STREAM, 0);

    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family      = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port        = htons(SERV_PORT);

    Bind(listenfd, (SA *) &servaddr, sizeof(servaddr));

    Listen(listenfd, LISTENQ);

    Signal(SIGCHLD, sig_chld);  /* must call waitpid() */

    for ( ; ; ) {
        clilen = sizeof(cliaddr);
        if ( (connfd = accept(listenfd, (SA *) &cliaddr, &clilen)) < 0) {
            if (errno == EINTR)
                continue;       /* back to for() */
            else
                err_sys("accept error");
        }

        if ( (childpid = Fork()) == 0) {    /* child process */
            Close(listenfd);    /* close listening socket */
            str_echo(connfd);   /* process the request */
            exit(0);
        }
        Close(connfd);          /* parent closes connected socket */
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章