ホームページ > php教程 > PHP开发 > Linux ゾンビプロセスの原因とそれを回避する方法

Linux ゾンビプロセスの原因とそれを回避する方法

高洛峰
リリース: 2016-12-17 11:39:06
オリジナル
1878 人が閲覧しました

プロセスにゾンビ ステータスを設定する目的は、子プロセスの情報を維持し、後で親プロセスが情報を取得できるようにすることです。この情報には、子プロセスのプロセス ID、終了ステータス、リソース使用率情報 (CPU 時間、メモリ使用量など) が含まれます。プロセスが終了し、ゾンビ状態の子プロセスがある場合、そのすべてのゾンビ子プロセスの親プロセス ID は 1 (初期プロセス) にリセットされます。これらの子プロセスを継承する init プロセスがそれらをクリーンアップします (init プロセスは子プロセスを待機するため、ゾンビ状態が削除されます)。

しかし通常、ゾンビ プロセスはカーネル内のスペースを占有し、最終的にはプロセス リソースを使い果たす可能性があります。では、なぜゾンビプロセスが生成されるのでしょうか?また、それを回避するにはどうすればよいでしょうか?以下ではこれら 2 つの側面を分析します。

ゾンビプロセスの理由

現在のプロセスで子プロセスを生成するには、通常、fork システムコールを呼び出す必要があることがわかっています。fork 関数の特徴は、1 回呼び出され、2 回返されることです。一度親プロセスに戻り、子プロセスに戻ったら、戻り値によって戻り点を判断できます:

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 コマンドを使用すると、図 1 に示すように、プロセスのステータスが Z (ゾンビを示す) であることがわかります。シェン シェン シェン シェン シェン シェン シェン シェン シェン シェン シェン シェン シェン シェン シェン シェン シェン シェン シェン シェン シェン シェン シェン シェン シェン シェパーズ;defunct> はゾンビプロセスを指定します。

コードは次のとおりです: re
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);
}
ログイン後にコピー

親プロセスを 600 秒スリープさせ、その後、サブプロセスが最初に終了します。最初に終了した子プロセスがゾンビ プロセスになることがわかります (プロセスのステータスは Z)。ゾンビプロセスの原因を理解したところで、ゾンビプロセスを回避する方法を見てみましょう。  linux僵尸进程

一般に、ゾンビプロセスの生成を防ぐには、子プロセスをフォークした後、子プロセスを待つ必要があります。同時に、子プロセスが終了すると、カーネルは親プロセスに 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);
}
ログイン後にコピー

次に、メイン関数で SIGCHLD シグナルの信号処理関数 (sig_chld) を登録します。その後、子プロセスが終了するとき、カーネルが SIGCHLD を送信すると、子プロセスはメイン関数によってキャプチャされます。 process を実行して信号処理関数 sig_chld を開始し、 sig_chld で wait を呼び出して、終了した子プロセスをクリーンアップします。こうすることで、終了した子プロセスがゾンビプロセスになることはありません。

アウト アウト アウト アウト アウト アウト アウト アウト アウト アウト アウト アウト アウト アウト アウト アウト アウト アウト アウト アウト アウト アウト アウト アウト アウト アウト アウト アウト アウト アウト アウトアウトアウトライトアウトライトアウトウェイアウトライトアウトライトアウトアウトアウトアウトアウトアウトアウトアウトアウトアウトアウトパーティーは、ss-vov wetynessw's-i'euse's-、e-を処理するために、接続されたものから外れていますクライアントとサーバーは、このクライアントからのリクエストを処理するために新しいプロセスを開始します。次に、クライアント プロセスがあり、サーバーへの複数のリクエストが開始され (5 つと仮定)、サーバーは 5 つの子プロセスをフォークしてクライアント入力を読み取り、処理します (同時に、クライアントがソケット、各子プロセスが終了します); クライアント プロセスを終了すると、カーネルはクライアント プロセスによって開かれたすべてのソケットを自動的に閉じ、クライアント プロセスによって開始された 5 つの接続は基本的に同時に終了します。これにより、接続ごとに 1 つずつ、合計 5 つの FIN が生成されます。サーバーがこれら 5 つの FIN を受信すると、5 つのサブプロセスは基本的に同時に終了します。これにより、図2に示すように、ほぼ同時に5つのSigchld信号が親プロセスに提出されます。

図 3 に示すように、最初にサーバー プログラムを実行し、次にクライアント プログラムを実行し、ps コマンドを使用してサーバーが 5 つの子プロセスをフォークしたことを確認します。

(图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中文网!

関連ラベル:
ソース:php.cn
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
人気のおすすめ
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート