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及时的将其回收。如果你不及时回收,那么它在系统中就会占用一个进程表项,如果这种僵尸进程过多,最后系统就没有可以用的进程表项,于是也无法再运行其它的程序。

相关推荐