Linux下的僵尸进程(Zombie)
在Linux中你可能进程听到有僵尸进程,那么究竟什么是僵尸进程,他又是怎样产生的呢?下面我们通过1个例子来说明一下。
我们知道退出一个进程用系统调用exit,但是这并不意味着该进程马上就消失了,事实上它还留下了一个被称为僵尸进程(Zombie)的数据结构。在Linux进程的5种状态中,僵尸进程是非常特殊的一种,它已经放弃了几乎所有内存空间,没有任何可执行代码,也不能被调度,仅仅在进程列表中保留一个位置,记载该进程的退出状态等信息供其他进程收集,除此之外,僵尸进程不再占有任何内存空间。从这点来看,僵尸进程虽然有一个很酷的名字,但它的影响力远远抵不上那些真正的僵尸兄弟,真正的僵尸总能令人感到恐怖,而僵尸进程却除了留下一些供人凭吊的信息,对系统毫无作用。
也许读者们还对这个新概念比较好奇,那就让我们来看一眼Linux里的僵尸进程究竟长什么样子。
当一个进程已退出,但其父进程还没有调用系统调用wait(稍后介绍)对其进行收集之前的这段时间里,它会一直保持僵尸状态,利用这个特点,我们来写一个简单的小程序:
#include sys/types.h>
#include unistd.h>
#include stdio.h>
/*
下面的程序展示了僵尸进程
进程关系:
主进程(main) ,主进程创建了父进程之后,就退出了
|
|-父进程 该父进程打印信息之后,一直循环 while(1)
|
|-子进程 该子进程创建之后打印就退出,成为僵死进程,因为他的父进程还在运行
*/
int main()
{
/*主进程创建子进程*/
pid_t child = fork();
/*在子进程中*/
if ( child == 0 )
{
pid_t pid;
char *message;
int n;
printf("fork program starting\n");
/*子进程再创建子进程*/
pid = fork();
switch( pid )
{
case -1:
perror("fork failed.\n");
exit(1);
break;
case 0:
message = "This is the son.\n";
n = 2;
break;
default:
message = "This is father.\n";
n = 5;
break;
}
/*父子进程都打印输出*/
for (; n > 0; n-- )
{
puts(message);
sleep(1);
}
if ( pid == 0) /*子进程退出, 成为僵死进程*/
exit(0);
else /*父进程一直循环*/
while(1);
}
else if ( child > 0) /*在主进程中,主进程退出,剩下了1个子进程和子进程的子进程*/
exit(0);
}
在这个程序中有三个进程,主进程,新进程(父进程)和他的子进程。主进程创建了新进程(父进程)之后立刻就退出了,他的资源会被init进程回收,现在只剩下了新进程(父进程)和它创建的子进程,而这个新进程(父进程)创建的子进程输出了一些信息之后就立刻就退出了,就只剩下了新进程(父进程),可是这个父进程并没有调用wait来回收它创建的子进程(虽然此时他的子进程已经退出了,但是仍在在进程表中占有一席之地,这个需要父进程用wait来回收),而此时此刻,这个父进程仍在死循环中(while(1);),于是他的子进程便成为僵尸进程, 我们用 ps aux就可以看到下面的东西:
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1923 29.0 0.1 2520 308 pts/4 R 00:41 0:09 ./zombie.o
root 1924 0.0 0.0 0 0 pts/4 Z 00:41 0:00 [zombie.o]
第2行的Z以及都表明这([zombie.o])是一个僵尸进程, 它的父进程./zombie.o一直在Running(R),
此时的僵尸进程我们已经无法用kill将其杀掉,要想将其杀掉,有两个方法,一个是重启机器,另一个就是变通的方法,杀掉它的父进程,当我们杀掉了它的父进程之后,这个僵尸的父进程就会变为init(所有的进程,当它的父进程被杀掉之后,它父进程的父进程就会接管它,以此类推,直到最后由init来接管它),而init进程是一个系统进程,它每隔一段时间就会用wait来回收这些没有父母的僵尸进程。在这里,当你用kill 1923杀掉它的父进程之后,你再用ps 命令就看不到它了。
我们就Linux的发展来看一看僵尸进程:我们知道,Linux和UNIX总有着剪不断理还乱的亲缘关系,僵尸进程的概念也是从UNIX上继承来的,而UNIX的先驱们设计这个东西并非是因为闲来无聊想烦烦其他的程序员。僵尸进程中保存着很多对程序员和系统管理员非常重要的信息,首先,这个进程是怎么死亡的?是正常退出呢,还是出现了错误,还是被其它进程强迫退出的?其次,这个进程占用的总系统CPU时间和总用户CPU时间分别是多少?发生页错误的数目和收到信号的数目。这些信息都被存储在僵尸进程中,试想如果没有僵尸进程,进程一退出,所有与之相关的信息都立刻归于无形,而此时程序员或系统管理员需要用到,就只好干瞪眼了。
那么,我们如何收集这些信息,并终结这些僵尸进程呢?就要靠我们下面要讲到的waitpid调用和wait调用。这两者的作用都是收集僵尸进程留下的信息,同时使这个进程彻底消失。
所以,防止僵尸进程的方法就是要记得调用系统调用wait及时的将其回收。如果你不及时回收,那么它在系统中就会占用一个进程表项,如果这种僵尸进程过多,最后系统就没有可以用的进程表项,于是也无法再运行其它的程序。