Linux下的僵尸进程和孤儿进程

说明

在UNIX里,除了进程0(即PID=0的交换进程,Swapper Process)以外的所有进程都是由其他进程使用系统调用fork创建的,这里调用fork创建新进程的进程即为父进程,而相对应的为其创建出的进程则为子进程,因而除了进程0以外的进程都只有一个父进程,但一个进程可以有多个子进程。

操作系统内核以进程标识符(Process Identifier,即PID)来识别进程。进程0是系统引导时创建的一个特殊进程,在其调用fork创建出一个子进程(即PID=1的进程1,又称init)后,进程0就转为交换进程(有时也被称为空闲进程),而进程1(init进程)就是系统里其他所有进程的祖先。

僵尸进程与孤儿进程

当一个子进程结束运行(一般是调用exit、运行时发生致命错误或收到终止信号所导致)时,子进程的退出状态(返回值)会回报给操作系统,系统则以SIGCHLD信号将子进程被结束的事件告知父进程,此时子进程的进程控制块(PCB)仍驻留在内存中。一般来说,收到SIGCHLD后,父进程会使用wait系统调用以取得子进程的退出状态,然后内核就可以从内存中释放已结束的子进程的PCB;而如若父进程没有这么做的话,子进程的PCB就会一直驻留在内存中,也即成为僵尸进程。

孤儿进程则是指父进程结束后仍在运行的子进程。在类UNIX系统中,孤儿进程一般会被init进程所“收养”,成为init的子进程。

为避免产生僵尸进程,实际应用中一般采取的方式是:

- 将父进程中对SIGCHLD信号的处理函数设为SIG_IGN(忽略信号);

- fork两次并杀死一级子进程,令二级子进程成为孤儿进程而被init所“收养”、清理。

Unix编程中所谓"僵尸进程"指什么,什么情况下会产生僵尸进程,如何杀掉僵尸进程?

在fork()/execve()过程中,假设子进程结束时父进程仍存在,而父进程fork()之前既没安装SIGCHLD信号处理函数调用wait()或waitpid()等待子进程结束,又没有显式忽略该信号,则子进程成为僵尸进程,无法正常结束。此时即使是root身份kill -9也不能杀死僵尸进程(僵死进程实际上是已死的进程,你当然不能杀死一个死人)。补救办法是杀死僵尸进程的父进程(僵尸进程的父进程必然存在),僵尸进程成为"孤儿进程",过继给1号进程init,init始终会负责清理僵尸进程。

●在一个进程调用了exit之后,该进程并非马上就消失掉,而是留下一个称为僵尸进程(Zombie)的数据结构。在Linux进程的5种状态中,僵尸进程是非常特殊的一种,它已经放弃了几乎所有内存空间,没有任何可执行代码,也不能被调度,仅仅在进程列表中保留一个位置,记载该进程的退出状态等信息供其他进程收集(如供父进程),除此之外,僵尸进程不再占有任何内存空间。从这点来看,僵尸进程虽然有一个很酷的名字,但它的影响力远远抵不上那些真正的僵尸兄弟,真正的僵尸总能令人感到恐怖,而僵尸进程却除了留下一些供人凭吊的信息,对系统毫无作用。

●系统调用exit,它的作用是使进程退出,但也仅仅限于将一个正常的进程变成一个僵尸进程,并不能将其完全销毁。僵尸进程虽然对其他进程几乎没有什么影响,不占用CPU时间,消耗的内存也几乎可以忽略不计,但有它在那里呆着,还是让人觉得心里很不舒服。而且Linux系统中进程数目是有限制的,在一些特殊的情况下,如果存在太多的僵尸进程,也会影响到新进程的产生。

●如何清理僵尸进程:用wait()和waitpid()系统调用可以清理僵尸进程,或者杀死僵尸进程的父进程(僵尸进程的父进程必然存在),僵尸进程成为"孤儿进程",过继给1号进程init,init始终会负责清理僵尸进程。

下面这段比喻形容了进程的一生,也更容易看出僵尸进程所处的阶段:

随着一句fork,一个新进程呱呱落地,但它这时只是老进程的一个克隆。

然后随着exec,新进程脱胎换骨,离家独立,开始了为人民服务的职业生涯。

人有生老病死,进程也一样,它可以是自然死亡,即运行到main函数的最后一个"}",从容地离我们而去;也可以是自杀,自杀有2种方式,一种是调用exit函数,一种是在main函数内使用return,无论哪一种方式,它都可以留下遗书,放在返回值里保留下;它还甚至能可被谋杀,被其它进程通过另外一些方式结束他的生命。

进程死掉以后,会留下一具僵尸,wait和waitpid充当了殓尸工,把僵尸推去火化,使其最终归于无形。

这就是进程完整的一生。

僵尸进程的危害

 由于子进程的结束和父进程的运行是一个异步过程,即父进程永远无法预测子进程 到底什么时候结束. 那么不会因为父进程太忙来不及wait子进程,或者说不知道子进程什么时候结束,而丢失子进程结束时的状态信息呢? 不会.因为UNIX提供了一种机制可以保证 只要父进程想知道子进程结束时的状态信息, 就可以得到. 这种机制就是:在每个进程退出的时候,内核释放该进程所有的资源,包括打开的文件,占用的内存等. 但是仍然为其保留一定的信息(包括进程号the process ID,退出状态the termination status of the process,运行时间the amount of CPU time taken by the process等), 直到父进程通过wait / waitpid来取时才释放. 但这样就导致了问题,如果你进程不调用wait / waitpid的话, 那么保留的那段信息就不会释放,其进程号就会一定被占用,但是系统所能使用的进程号是有限的,如果大量的产生僵死进程,将因为没有可用的进程号而导致系统不能产生新的进程. 此即为僵尸进程的危害应当避免。

处理:

*显式忽略SIGCHLD信号是指类似这样的代码:

signal( SIGCHLD, SIG_IGN );

*安装SIGCHLD信号句柄是指类似这样的代码:

当然,不建议使用signal(),应该使用sigaction()。

僵尸进程中保存着很多对程序员和系统管理员非常重要的信息,首先,这个进程是怎么死亡的?是正常退出呢,还是出现了错误,还是被其它进程强迫退出的?其次,这个进程占用的总系统CPU时间和总用户CPU时间分别是多少?发生页错误的数目和收到信号的数目。这些信息都被存储在僵尸进程中,试想如果没有僵尸进程,进程一退出,所有与之相关的信息都立刻归于无形,而此时程序员或系统管理员需要用到,就只好干瞪眼了。

那么,我们如何收集这些信息,并终结这些僵尸进程呢?就要靠waitpid调用和wait调用。这两者的作用都是收集僵尸进程留下的信息,同时使这个进程彻底消失。

pid_t wait(int *status)

1)进程一旦调用了wait,就立即阻塞自己,由wait自动分析是否当前进程的某个子进程已经退出,如果让它找到了这样一个已经变成僵尸的子进程,wait就会收集这个子进程的信息,并把它彻底销毁后返回;如果没有找到这样一个子进程,wait就会一直阻塞在这里,直到有一个出现为止。

参数status用来保存被收集进程退出时的一些状态,它是一个指向int类型的指针。但如果我们对这个子进程是如何死掉的毫不在意,只想把这个僵尸进程消灭掉,(事实上绝大多数情况下,我们都会这样想),我们就可以设定这个参数为NULL,就象下面这样:

pid = wait(NULL);

如果成功,wait会返回被收集的子进程的进程ID,如果调用进程没有子进程,调用就会失败,此时wait返回-1,同时errno被置为ECHILD。

参数status

如果参数status的值不是NULL,wait就会把子进程退出时的状态取出并存入其中,这是一个整数值(int),指出了子进程是正常退出还是被非正常结束的(一个进程也可以被其他进程用信号结束),以及正常结束时的返回值,或被哪一个信号结束的等信息。由于这些信息被存放在一个整数的不同二进制位中,所以用常规的方法读取会非常麻烦,人们就设计了一套专门的宏(macro)来完成这项工作,下面我们来学习一下其中最常用的两个:

● WIFEXITED(status)

这个宏用来指出子进程是否为正常退出的,如果是,它会返回一个非零值。

(请注意,虽然名字一样,这里的参数status并不同于wait唯一的参数--指向整数的指针status,而是那个指针所指向的整数,切记不要搞混了。)

● WEXITSTATUS(status)

当WIFEXITED返回非零值时,我们可以用这个宏来提取子进程的返回值,如果子进程调用exit(5)退出,WEXITSTATUS (status)就会返回5;如果子进程调用exit(7),WEXITSTATUS(status)就会返回7。请注意,如果进程不是正常退出的,也就是说,WIFEXITED返回0,这个值就毫无意义。

Linux中的进程基本状态:

1、执行(R)状态:CPU正在执行,即进程正在占用CPU。

2、就绪(W)状态:进程已经具备的执行的一切条件,正在等待分配CPU的处理时间片。

3、停止(S)状态:进程不能使用CPU。

三个函数:

fork();功能:创建一个新的进程。(fork()<0[出错]、fork()==0[子进程]、fork()>0[父进程]

wait();功能:真正结束进程(收尸)。

exec();功能:执行外部程序。

孤儿进程

孤儿进程:一个父进程退出,而它的一个或多个子进程还在进行,那么那些子进程将成了孤儿进程,孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作。僵尸资源会造成资源浪费,孤儿进程则不会。

链接:

http://www.cnblogs.com/mydomain/archive/2010/10/24/1859826.html

http://gengning938.blog.163.com/blog/static/12822538120116188816625/

时间: 2025-02-01 19:59:15

Linux下的僵尸进程和孤儿进程的相关文章

Linux下杀僵尸进程办法

1) 检查当前僵尸进程信息 # ps -ef | grep defunct | grep -v grep | wc -l 175 # top | head -2 top - 15:05:54 up 97 days, 23:49, 4 users, load average: 0.66, 0.45, 0.39 Tasks: 829 total, 1 running, 479 sleeping, 174 stopped, 175 zombie # ps -ef | grep defunct | gr

[转] linux下的僵尸进程处理SIGCHLD信号

什么是僵尸进程? 首先内核会释放终止进程(调用了exit系统调用)所使用的所有存储区,关闭所有打开的文件等,但内核为每一个终止子进程保存了一定量的信息.这些 信息至少包括进程ID,进程的终止状态,以及该进程使用的CPU时间,所以当终止子进程的父进程调用wait或waitpid时就可以得到这些信息. 而僵尸进程就是指:一个进程执行了exit系统调用退出,而其父进程并没有为它收尸(调用wait或waitpid来获得它的结束状态)的进程. 任何一个子进程(init除外)在exit后并非马上就消失,而是

linux下的僵尸进程处理SIGCHLD信号

什么是僵尸进程? 首先内核会释放终止进程(调用了exit系统调用)所使用的所有存储区,关闭所有打开的文件等,但内核为每一个终止子进程保存了一定量的信息.这些信息至少包括进程ID,进程的终止状态,以及该进程使用的CPU时间,所以当终止子进程的父进程调用wait或waitpid时就可以得到这些信息. 而僵尸进程就是指:一个进程执行了exit系统调用退出,而其父进程并没有为它收尸(调用wait或waitpid来获得它的结束状态)的进程. 任何一个子进程(init除外)在exit后并非马上就消失,而是留

Linux中的僵尸进程和孤儿进程

在UNIX里,除了进程0(即PID=0的交换进程,Swapper Process)以外的所有进程都是由其他进程使用系统调用fork创建的,这里调用fork创建新进程的进程即为父进程,而相对应的为其创建出的进程则为子进程,因而除了进程0以外的进程都只有一个父进程,但一个进程可以有多个子进程.        操作系统内核以进程标识符(Process Identifier,即PID)来识别进程.进程0是系统引导时创建的一个特殊进程,在其调用fork创建出一个子进程(即PID=1的进程1,又称init)

linux的僵尸进程和孤儿进程

1 僵尸进程: 子进程已经退出勒 但是还没有回收资源的进程为僵尸进程 代码验证 1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <unistd.h> 4 5 int main() 6 { 7 pid_t pid; 8 if((pid=fork())==-1) 9 { 10 perror("fork error"); 11 12 }else if(pid==0){ 13 printf(&quo

【转】僵尸进程和孤儿进程

之前写了进程的创建,由于是半年前做的题目了,现在回忆有点生疏,有些概念都忘了,比如僵尸进程.孤儿进程.子进程和父进程的关系...在博客园看到有一篇讲的很好的关于僵尸进程和孤儿进程的文章,没看到有转载的选项,直接复制过来了,方便以后忘了再查看 转自http://www.cnblogs.com/Anker/p/3271773.html 1.前言 之前在看<unix环境高级编程>第八章进程时候,提到孤儿进程和僵尸进程,一直对这两个概念比较模糊.今天被人问到什么是孤儿进程和僵尸进程,会带来什么问题,怎

Linux-进程描述(3)之进程状态僵尸进程与孤儿进程

进程状态 进程状态反映进程执行过程的变化.这些状态随着进程的执行和外界条件的变化而转换.为了弄明正正在运行的进程是什么意思,我们需要知道进程的不同状态.一个进程可以有多个状态(在Linux内核中,进程有时候也叫做任务).下面的状态在 fs/proc/array.c 文件中定义: /* * The task state array is a strange "bitmap" of * reasons to sleep. Thus "running" is zero,

zombie僵尸进程、孤儿进程和守护进程

以前一直对僵尸进程和孤儿进程都没怎么理解,真是罪过,最近在看liunx编程设计(第四版),看到了他们的概念,所以对它们做个总结!加深印象. 基本概念: 我们知道在unix/linux中,正常情况下,子进程是通过父进程创建的,子进程再创建新的进程.子进程的结束和父进程的运行是一个异步过程,即父进程永远无法预测子进程 到底什么时候结束. 当一个 进程完成它的工作终止之后,它的父进程需要调用wait()或者waitpid()系统调用取得子进程的终止状态. 但是如果情况不是这样的会怎么样呢,毕竟世事难料

僵尸进程和孤儿进程-(转自Anker&#39;s Blog)

2.基本概念 我们知道在unix/linux中,正常情况下,子进程是通过父进程创建的,子进程在创建新的进程.子进程的结束和父进程的运行是一个异步过程,即父进程永远无法预测子进程 到底什么时候结束. 当一个 进程完成它的工作终止之后,它的父进程需要调用wait()或者waitpid()系统调用取得子进程的终止状态. 孤儿进程:一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程.孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作. 僵尸进程