僵尸进程
什么是僵尸进程
提到僵尸,首先想到的肯定是僵尸系列的电影或者植物大战僵尸。一个活人为什么会成为僵尸?一般普通人是被僵尸攻击了,在变成僵尸之前没有得到有效处理(比如击毙),等他被僵尸病毒侵入脑子后就成为僵尸一员了。
那什么是僵尸进程呢?同僵尸人一样,一个进程由于各种原因终止后,没有得到有效处理,就成为了僵尸进程。特别需要说明的是,linux系统中,子进程的终止状态会发送给父进程,意味着只有父进程可以有效处理子进程。如果父进程没有处理,子进程变成僵尸进程。当然,如果父进程也终止了,子进程就成了孤儿进程,变成init进程的子进程,此时也就没有僵尸进程了。
下面通过一个实例来说明僵尸进程的产生过程。
/**
* zombie.c
* 显示僵尸进程的产生过程
**/
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main(int argc, char **argv[])
{
pid_t pid;
pid = fork();
if (pid < 0)
{
perror("fork error");
exit(1);
}
else if (pid == 0) // child process
{
printf("child process %d, parent process %d\n", getpid(), getppid());
printf("after child process died....\n");
exit(0);
}
else // parent process
{
sleep(1);
printf("parent process %d\n", getpid());
system("ps -o pid,ppid,state,tty,command");
printf("parent process is exiting\n");
exit(0);
}
exit(0);
}
编译和运行程序,如下:
/source/zombie# gcc zombie.c -o zombie
/source/zombie# ./zombie
child process 10750, parent process 10749
after child process died....
parent process 10749
PID PPID S TT COMMAND
473 455 S pts/3 sudo su
474 473 S pts/3 su
475 474 S pts/3 bash
562 475 S pts/3 ./daytimetcpsrv
10749 475 S pts/3 ./zombie
10750 10749 Z pts/3 [zombie] <defunct>
10751 10749 S pts/3 sh -c ps -o pid,ppid,state,tty,command
10752 10751 R pts/3 ps -o pid,ppid,state,tty,command
parent process is exiting
简要说明一下程序。
fork之后,先让父进程sleep 1秒,确保子进程先运行。子进程先结束后,父进程中调用shell指令ps,查看当前进程状态,显而易见,子进程10750已经处于Z状态,即僵尸状态(10750 10749 Z pts/3 [zombie] )。
僵尸进程的危害
说了这么多,僵尸进程会有危害吗?坦白讲,单个的僵尸进程貌似并没什么危害,但是考虑这样一种场景,父进程一直循环产生子进程,而且不处理子进程的退出状态,会是什么样的情况?此时,成千上万的子进程退出后成为了僵尸进程,僵尸进程会一直占据资源(内存,文件描述符等)不释放,久而久之,系统就会因为资源耗尽而崩溃。
僵尸进程解决办法
说了僵尸进程的危害,那么,有什么办法可以解决僵尸进程呢?
当然有!
信号机制
最容易想到的办法,就是当子进程终止时,会发送自己的状态给父进程。父进程处理子进程的状态即可。
/**
* waitpid.c
* 僵尸进程解决方法:父进程接收子进程信号
**/
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/wait.h>
static void sig_child(int signo);
int main(int argc, char **argv[])
{
pid_t pid;
signal(SIGCHLD, sig_child); // catch child signal
pid = fork();
if (pid < 0)
{
perror("fork error");
exit(1);
}
else if (pid == 0) // child process
{
printf("child process %d is exiting\n", getpid());
exit(0);
}
else // parent process
{
printf("parent process %d\n", getpid());
sleep(2);
system("ps -o pid,ppid,state,tty,command");
printf("parent process is exiting\n");
exit(0);
}
exit(0);
}
static void sig_child(int signo)
{
pid_t pid;
int stat;
while ((pid = waitpid(pid, &stat, WNOHANG)) > 0)
{
printf("child process %d terminated\n", pid);
}
}
编译和运行程序,如下:
/source/zombie# gcc waitpid.c -o waitpid
/source/zombie# ./waitpid
parent process 11068
child process 11069 is exiting
child process 11069 terminated
PID PPID S TT COMMAND
473 455 S pts/3 sudo su
474 473 S pts/3 su
475 474 S pts/3 bash
562 475 S pts/3 ./daytimetcpsrv
11068 475 S pts/3 ./waitpid
11070 11068 S pts/3 sh -c ps -o pid,ppid,state,tty,command
11071 11070 R pts/3 ps -o pid,ppid,state,tty,command
parent process is exiting
如上所示,子进程安全退出,ps显示此时已经没有僵尸进程了。
fork两次
当然,还可以找到一直产生僵尸进程的罪魁祸首,也就是它们的父进程,把此进程杀死,从而让僵尸进程成为孤儿进程。此时,init进程收养和妥善处理这些孤儿进程。
/**
* init.c
* 僵尸进程解决方法:fork两次,让僵尸进程成为孤儿进程
**/
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <signal.h>
int main(int argc, char **argv[])
{
pid_t pid;
pid = fork();
if (pid < 0)
{
perror("fork error");
exit(1);
}
else if (pid == 0)
{
printf("I am first child process %d\n", getpid());
pid = fork();
if (pid < 0)
{
perror("fork error");
exit(2);
}
else if (pid > 0)
{
printf("first child %d is exited\n", getpid());
exit(0);
}
sleep(3);
printf("I am second child process %d, parent process %d\n", getpid(), getppid());
exit(0);
}
else
{
if (waitpid(pid, NULL, 0) != pid)
{
perror("waitpid error");
exit(1);
}
}
exit(0);
}
编译和运行程序,如下:
/source/zombie# gcc init.c -o init
/source/zombie# ./init
I am first child process 11305
first child 11305 is exited
/source/zombie# I am second child process 11306, parent process 1
^C
如上,可以看出,第一个子进程发送的信号被父进程处理后,安全退出。在第一个子进程中创建的子进程成为孤儿进程,被init进程收养,成功解决僵尸进程的问题。