Der Zweck des Festlegens des Zombie-Status für einen Prozess besteht darin, die Informationen des untergeordneten Prozesses beizubehalten, damit der übergeordnete Prozess sie zu einem späteren Zeitpunkt abrufen kann. Zu diesen Informationen gehören die Prozess-ID des untergeordneten Prozesses, der Beendigungsstatus und Informationen zur Ressourcennutzung (CPU-Zeit, Speichernutzung usw.). Wenn ein Prozess beendet wird und sich untergeordnete Prozesse im Zombie-Zustand befinden, werden die übergeordneten Prozess-IDs aller seiner untergeordneten Zombie-Prozesse auf 1 zurückgesetzt (Init-Prozess). Der Init-Prozess, der diese untergeordneten Prozesse erbt, bereinigt sie (der Init-Prozess wartet auf sie und entfernt dadurch den Zombie-Status).
Aber normalerweise sind wir nicht bereit, Zombie-Prozesse zu behalten. Sie belegen Platz im Kernel und können dazu führen, dass uns irgendwann die Prozessressourcen ausgehen. Warum werden also Zombie-Prozesse generiert und wie kann man sie vermeiden? Ich werde diese beiden Aspekte im Folgenden analysieren.
Gründe für Zombie-Prozesse
Wir wissen, dass wir zum Generieren eines untergeordneten Prozesses im aktuellen Prozess im Allgemeinen den Fork-Systemaufruf aufrufen müssen. Das Besondere an der Fork-Funktion ist, dass dies der Fall ist einmal und zweimal aufgerufen Rückkehr, einmal zum übergeordneten Prozess und einmal zum untergeordneten Prozess Wir können den Rückgabepunkt anhand des Rückgabewerts beurteilen:
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 "); }
Wenn der untergeordnete Prozess vor dem übergeordneten Prozess beendet wird, und der Der übergeordnete Prozess funktioniert nicht. Wenn wait/waitpid aufgerufen wird, wird der untergeordnete Prozess zu einem Zombie-Prozess. Durch den ps-Befehl können wir sehen, dass der Status des Prozesses Z ist (zeigt Zombie an), wie in Abbildung 1 dargestellt:
(Abbildung 1)
Hinweis: Einige Unix-Systeme verwenden
Der Code lautet wie folgt:
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); }
Lassen Sie den übergeordneten Prozess 600 Sekunden lang ruhen, und dann wird der untergeordnete Prozess zuerst beendet. Wir können sehen, dass der untergeordnete Prozess, der zuerst beendet wurde, zu einem Zombie wird Prozess (Prozessstatus ist Z)
Zombie-Prozesse vermeiden
Wir kennen die Gründe, warum Zombie-Prozesse generiert werden.
Um zu verhindern, dass Zombie-Prozesse generiert werden, müssen wir nach dem Fork gleichzeitig auf die untergeordneten Prozesse warten. Wenn der untergeordnete Prozess beendet wird, gibt der Kernel dem übergeordneten Prozess ein SIGCHLD Signal, damit wir eine Erfassung erstellen können SIGCHLD Die Signalverarbeitungsfunktion des Signals kann den austretenden untergeordneten Prozess bereinigen, indem sie im Funktionskörper wait (oder waitpid) aufruft, um Zombie-Prozesse zu verhindern. Wie im folgenden Code gezeigt:
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); }
Registrieren Sie nun eine Signalverarbeitungsfunktion (sig_chld) für das SIGCHLD-Signal in der Hauptfunktion und senden Sie sie dann, wenn der untergeordnete Prozess beendet wird und der Kernel ein SIGCHLD übermittelt wird vom Hauptprozess erfasst. Geben Sie die Signalverarbeitungsfunktion sig_chld ein und rufen Sie dann wait in sig_chld auf, um den verlassenen untergeordneten Prozess zu bereinigen. Auf diese Weise wird der verlassene untergeordnete Prozess nicht zu einem Zombie-Prozess.
Selbst wenn wir das SIGCHLD-Signal erfassen und „Warten“ aufrufen, um den Exit-Prozess zu bereinigen, können wir die Erzeugung von Zombie-Prozessen immer noch nicht vollständig vermeiden:
Wir Angenommen, es gibt ein Client-/Serverprogramm. Für jeden verbundenen Client startet der Server einen neuen Prozess, um die Anfrage dieses Clients zu bearbeiten. Dann haben wir einen Client-Prozess. In diesem Prozess werden mehrere Anfragen an den Server initiiert (angenommen, 5), dann wird der Server 5 untergeordnete Prozesse verzweigen, um die Client-Eingabe zu lesen und sie zu verarbeiten (gleichzeitig, wenn der Client schließt). socket , jeder untergeordnete Prozess wird beendet); wenn wir den Client-Prozess beenden, schließt der Kernel automatisch alle vom Client-Prozess geöffneten Sockets, dann werden die 5 vom Client-Prozess initiierten Verbindungen grundsätzlich gleichzeitig beendet. Dadurch werden 5 FINs generiert, einer für jede Verbindung. Wenn der Server diese 5 FINs empfängt, werden die 5 Unterprozesse grundsätzlich gleichzeitig beendet. Dies führt dazu, dass 5 SIGCHLD-Signale fast gleichzeitig an den übergeordneten Prozess übermittelt werden, wie in Abbildung 2 dargestellt:
(Abbildung 2)
Genau Diese Übermittlung mehrerer Instanzen desselben Signals führt zu dem Problem, das wir uns gleich ansehen werden.
Wir führen zuerst das Serverprogramm und dann das Clientprogramm aus und verwenden den Befehl ps, um festzustellen, dass der Server 5 untergeordnete Prozesse gegabelt hat, wie in Abbildung 3 dargestellt:
(图3)
然后我们Ctrl+C终止客户端进程,在我机器上边测试,可以看到信号处理函数运行了3次,还剩下2个僵尸进程,如图4:
(图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中文网!