您的位置:首页 > 运维架构 > Linux

【Linux】僵尸进程与孤儿进程

2017-04-18 17:34 337 查看


     今天要给大家介绍两个悲伤的进程:僵尸进程和孤儿进程。话不多说,直接介绍。
     之前的博客有讲过保存进程信息的一个重要的数据结构,task_struct结构体,其中,state表示进程可能出现的状态。如下:
static const char * const task_ state _array[]=
{
     R(running),
     S(sleeping), 
     D(disk sleep),
     T(stopped),
     t(tracing stop),
     X(dead),
     Z(zombie)

}

我们对个别状态进行解释如下:
     S(sleeping) ——浅度睡眠,也是不可中断睡眠。
     D(disk sleep)——也是一种sleep状态,是深度睡眠,也是可中断睡眠。正常情况下,只要该进程处于D状态,谁都不能操作它了,除非是进程自己想退出这种状态了才能退出,可谓刀枪不入。这种状态在I/O中出现居多,例如某进程在向硬盘请求读数据时,在等待期间,该进程就应处于D状态。不然在等待期间该进程被外界强行结束了,那么从硬盘读取出的数据将无处安放,为避免此情况发生,就应将此时的进程设为D状态,让外界无法操作、刀枪不入。
   
Z(zombie) ——僵尸状态
     我们把子进程退出一直到父进程过来检验子进程的这段时间的状态叫做僵尸状态。由于子进程的结束和父进程的运行是一个异步过程,即父进程永远无法预测子进程到底什么时候结束. 那么会不会因为父进程太忙来不及wait子进程,或者说不知道子进程什么时候结束,而丢失子进程结束时的状态信息(包括:退出的原因、是否异常退出、有无做完自身工作等等)呢?

     当然不会。因为Linux提供了一种机制可以保证只要父进程想知道子进程结束时的状态信息, 就可以得到。这种机制就是: 在每个进程退出的时候,内核只先释放该进程所有的资源,包括打开的文件,占用的内存等。但PCB并不会被立即释放,因为PCB保存着子进程退出时的状态信息,直到父进程通过wait / waitpid来取子进程的状态信息时,才释放PCB.

     但这样就导致了问题,如果父进程一直不调用wait / waitpid的话,那么保留子进程状态信息的PCB就不会释放,此子进程的进程号就会一直被占用,但是系统所能使用的进程号是有限的,如果大量的产生僵死进程,将因为没有可用的进程号而导致系统不能产生新的进程. 此即为僵尸进程的危害,应当避免。
 
让我们看一个僵尸进程的例子:

 1>用makefile生成一个可执行文件jiangshi ,执行./jiangshi (呵呵~~忽略渣渣命名)



僵尸进程的代码jiangshi.c如下:

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
int main()
{
pid_t id = fork(); // 调用fork()创建子进程
if( id == 0) {//子进程

sleep(3);
printf("I'm a child\n"); //子进程运行3s后结束

}
else if( id > 0 ){//父进程
while(1)
{
sleep(3);
printf("I'm a father\n"); //父进程一直运行
}

}
else{
printf("fail~~~\n");

}

return 0;

}


其执行结果如下:



恩,跟我们预想的一样,子进程创建成功后,父进程和子进程一起运行3s,3s后子进程结束,父进程并未结束而是继续运行,那么这个时候子进程就变成僵尸进程了。下面我们来验证一下。

2>再打开一个命令行(Terminal),输入指令ps -al观察此时进程的状态



可以知道:

①子进程变成了僵尸进程,不仅其状态位变成了Z(zombie) ,而且子进程后还跟了一个<defunct>,表示该进程已死亡,已废止  。
②如果这个僵尸进程一直得不到父进程通过wait / waitpid来取它的状态信息,那么它就会一只占用着这个4207(PID)的进程号。如果产生大量僵死进程,就会因为没有可用的进程号而导致系统不能产生新的进程,
所以应当避免僵尸进程。

下面,我们在讲第二个重要的进程,孤儿进程。

孤儿进程
        一个父进程结束,而它的子进程还在运行,那么那些还在运行的子进程将成为孤儿进程,孤儿进程将被Init进程(进程号为1的进程)所收养,并由Init进程对它们完成状态收集工作。

说明:
①由之前僵尸进程的知识我们可以知道,父进程结束后也会变成僵尸进程,由子进程的“爷爷进程”通过wait / waitpid来取父进程的状态信息,而这里的“爷爷进程”其实就是Bash。
②那么也一样,孤儿进程结束后,会由领养它的Init进程通过wait / waitpid来取孤儿进程的状态信息

让我们看一个孤儿进程的例子:

 1>用makefile生成一个可执行文件myfile,执行./myfile



孤儿进程的代码myfile.c如下:

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
int main()
{
pid_t id = fork(); // 调用fork()创建子进程
if( id == 0) {//子进程
while(1)
{
sleep(3);
printf("I'm a child\n"); //子进程一直运行
}
}
else if( id > 0 ){//父进程
sleep(3);
printf("I'm a father\n"); //父进程运行3s后结束
}
else{
printf("fail~~~\n");

}

return 0;

}


 其执行结果如下:



我们可以看到,跟预想的一样,子进程创建成功后,父进程和子进程一起运行3s,3s后 父进程结束,子进程并未结束而是继续运行,那么子进程就成了孤儿进程。我们用下面的方法进行验证。

2>跟僵尸进程验证方法一样,再打开一个命令行(Terminal),输入指令ps -al观察此时进程的状态



我们会发现两点:

①孤儿进程确实被Init进程(进程号为1的进程)所收养。
②没有显示变成僵尸进程的父进程,说明父进程被子进程的“爷爷进程”Bash,通过wait / waitpid来取走状态信息后释放了。

     
       由于我也是刚刚接触Linux,知识储备还很不足,文中可能会有错误,如果出现错误的话,请务必告诉我~~~~


内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息