Maison php教程 PHP开发 linux僵尸进程产生的原因以及如何避免产生僵尸进程

linux僵尸进程产生的原因以及如何避免产生僵尸进程

Dec 17, 2016 am 11:39 AM

给进程设置僵尸状态的目的是维护子进程的信息,以便父进程在以后某个时间获取。这些信息包括子进程的进程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 ");
}
Copier après la connexion

如果子进程先于父进程退出, 同时父进程又没有调用wait/waitpid,则该子进程将成为僵尸进程。通过ps命令,我们可以看到该进程的状态为Z(表示僵死),如图1所示:

 linux僵尸进程

(图1)

备注: 有些unix系统在ps命令输出的COMMAND栏以指明僵尸进程。

代码如下:

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);
}
Copier après la connexion

让父进程休眠600s, 然后子进程先退出,我们就可以看到先退出的子进程成为僵尸进程了(进程状态为Z)

避免产生僵尸进程

我们知道了僵尸进程产生的原因,下边我们看看如何避免产生僵尸进程。

一般,为了防止产生僵尸进程,在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);
}
Copier après la connexion

现在main函数中给SIGCHLD信号注册一个信号处理函数(sig_chld),然后在子进程退出的时候,内核递交一个SIGCHLD的时候就会被主进程捕获而进入信号处理函数sig_chld,然后再在sig_chld中调用wait,就可以清理退出的子进程。这样退出的子进程就不会成为僵尸进程。

然后,即便我们捕获SIGCHLD信号并且调用wait来清理退出的进程,仍然不能彻底避免产生僵尸进程;我们来看一种特殊的情况:

我们假设有一个client/server的程序,对于每一个连接过来的client,server都启动一个新的进程去处理来自这个client的请求。然后我们有一个client进程,在这个进程内,发起了多个到server的请求(假设5个),则server会fork 5个子进程来读取client输入并处理(同时,当客户端关闭套接字的时候,每个子进程都退出);当我们终止这个client进程的时候 ,内核将自动关闭所有由这个client进程打开的套接字,那么由这个client进程发起的5个连接基本在同一时刻终止。这就引发了5个FIN,每个连接一个。server端接受到这5个FIN的时候,5个子进程基本在同一时刻终止。这就又导致差不多在同一时刻递交5个SIGCHLD信号给父进程,如图2所示:

 linux僵尸进程

(图2)

正是这种同一信号多个实例的递交造成了我们即将查看的问题。

我们首先运行服务器程序,然后运行客户端程序,运用ps命令看以看到服务器fork了5个子进程,如图3:

 linux僵尸进程

(图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);
}
Copier après la connexion



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

Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn

Outils d'IA chauds

Undresser.AI Undress

Undresser.AI Undress

Application basée sur l'IA pour créer des photos de nu réalistes

AI Clothes Remover

AI Clothes Remover

Outil d'IA en ligne pour supprimer les vêtements des photos.

Undress AI Tool

Undress AI Tool

Images de déshabillage gratuites

Clothoff.io

Clothoff.io

Dissolvant de vêtements AI

Video Face Swap

Video Face Swap

Échangez les visages dans n'importe quelle vidéo sans effort grâce à notre outil d'échange de visage AI entièrement gratuit !

Outils chauds

Bloc-notes++7.3.1

Bloc-notes++7.3.1

Éditeur de code facile à utiliser et gratuit

SublimeText3 version chinoise

SublimeText3 version chinoise

Version chinoise, très simple à utiliser

Envoyer Studio 13.0.1

Envoyer Studio 13.0.1

Puissant environnement de développement intégré PHP

Dreamweaver CS6

Dreamweaver CS6

Outils de développement Web visuel

SublimeText3 version Mac

SublimeText3 version Mac

Logiciel d'édition de code au niveau de Dieu (SublimeText3)