Home  >  Article  >  php教程  >  What causes Linux zombie processes and how to avoid them

What causes Linux zombie processes and how to avoid them

高洛峰
高洛峰Original
2016-12-17 11:39:061858browse

The purpose of setting zombie status for a process is to maintain the information of the child process so that the parent process can obtain it at a later time. This information includes the child process's process ID, termination status, and resource utilization information (CPU time, memory usage, etc.). If a process terminates and has child processes in the zombie state, the parent process IDs of all its zombie child processes will be reset to 1 (init process). The init process that inherits these child processes will clean them up (the init process will wait for them, thus removing the zombie state).

But usually, we are not willing to keep zombie processes. They occupy space in the kernel and may eventually cause us to run out of process resources. So why are zombie processes generated and how to avoid them? I will analyze these two aspects below.

The reason for the zombie process

We know that to generate a child process in the current process, we generally need to call the fork system call. The special thing about the fork function is that it calls it once, returns twice, and returns to the parent process once. , once returned to the child process, we can judge its return point by the return value:

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 ");
}

If the child process exits before the parent process, and the parent process does not call wait/waitpid, the child process will become a zombie process. Through the ps command, we can see that the status of the process is Z (meaning zombie), as shown in Figure 1:

 linux僵尸进程

                                                                                                                                    in,       ,                          -- she herself sheep sheep she plays she her in sheen by herself. ;defunct> specifies a zombie process.

The code is as follows: 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);
}

let the father's process sleep 600s, and then the sub -process exits first. We can see that the child process that quit first becomes a zombie process (the process status is Z)

Avoid the creation of zombies. Now that we have understood the causes of zombie processes, let’s look at how to avoid zombie processes.

Generally, in order to prevent zombie processes from being generated, we have to wait for the child processes after forking them; at the same time, when the child process exits, the kernel will give the parent process a SIGCHLD signal, so we can establish a signal processing that captures the SIGCHLD signal. Function, by calling wait (or waitpid) in the function body, you can clean up the exited child processes to prevent zombie processes. As shown in the following code:

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);
}

Now register a signal processing function (sig_chld) for the SIGCHLD signal in the main function, and then when the child process exits, when the kernel submits a SIGCHLD, it will be captured by the main process and enter the signal processing function sig_chld , and then call wait in sig_chld to clean up the exited child process. In this way, the exited child process will not become a zombie process.

                                                                    out out out of   through use     out out   out out through out Through out   out through out   out through out out Through outcepsps out outmb right out out out way out out right out right out way out out right out right out out out out out out out out out out out out out out outs outs party to s ss-vOv between-nessw's-i'euse's-,e- to toe-to-use to handle to handle to look out over from to handle to handle out. The connected client and server start a new process to handle the request from this client. Then we have a client process. In this process, multiple requests to the server are initiated (assuming 5), then the server will fork 5 child processes to read the client input and process it (at the same time, when the client closes the socket , each child process exits); when we terminate the client process, the kernel will automatically close all sockets opened by the client process, then the 5 connections initiated by the client process will basically terminate at the same time. This raises 5 FINs, one for each connection. When the server receives these 5 FINs, the 5 sub-processes basically terminate at the same time. This in turn results in 5 SIGCHLD signals being submitted to the parent process at almost the same time, as shown in Figure 2:

                                                                                                                                                              through 5 SIGCHLD signals being submitted to the parent process at almost the same time. The problem.

We first run the server program, then run the client program, and use the ps command to see that the server has forked 5 child processes, as shown in Figure 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);
}



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

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn