版权声明:本文为博主原创文章,转载请注明博客地址: https://cloud.tencent.com/developer/article/1433350
僵尸进程就是已经结束的进程(几乎不占计算机资源),但是它并没有从进程列表中删除。僵尸进程太多会导致操作系统的进程数目过多,从而占满了OS的进程表。进而导致无法创建新进程,致使OS崩溃。
僵尸进程几乎不占资源,它没有可执行代码,也不能被调度,但是它占据着进程表中的一个位置,记载这该进程的PCB信息。它需要等待他的父进程来终结它。一旦它的父进程是一个循环,不会结束(父进程不去调用wait函数或者waitpid函数)。那么子进程将会一直保持僵尸状态。那么它将一直占用进程号,系统就没法回收利用。
在Linux下使用top命令可以产看当前进程数目,以及进程的状态。例如:
可以看到我的系统暂时并没有僵尸进程(zombie) 。挂起的进程倒是一大堆。
僵尸进程产生的原因:每个Linux进程在进程表中都有一个进入点,内核执行该进程时,使用到的一切信息都存入在进程点。我们可以使用ps命令来查看进程状态。当一个父进程以fork()系统调用建立一个新的子进程后,核心进程就会在进程表中给这个子进程分配一个进入点,然后将相关信息存储在该进入点所对应的进程表内。这些信息中有一项是其父进程的识别码。而当这个子进程结束的时候(调用exit命令结束),其实他并没有真正的被销毁,而是留下一个僵尸进程的。此时原来进程表中的数据会被该进程的退出码(exit code)、执行时所用的CPU时间等数据所取代,这些数据会一直保留到系统将它传递给它的父进程为止。
我们用下面的代码来产生僵尸进程
#include<sys/types.h>
#include<stdio.h>
#include<unistd.h>
int main()
{
while(1)
{
pid_t zombie = fork();
if(0 == zombie)
{
execl("/bin/bash","bash","-c","ls",NULL);
}
sleep(1);
}
return 0;
}
运行结果如下:
会一直在终端上打印当前目录下的文件。同时我们另开一个终端,输入top命令,将会看到zombie进程的数量在一直增长。如下图所示:
如何避免僵尸进程:
使用wait函数和waitpid函数。
wait函数:需要头文件#include<sys/wait.h>
函数原型:pid_t wait(int *status);
函数功能:阻塞(睡眠)进程,等待子进程结束,负责为子进程回收资源。参数是接受的子进程退出状态,返回值是子进程的PID,出错为-1。
我们主要使用两个宏来提取status里保存的子进程的退出信息。
WEXITSTATUS(status);//从status中提取出子进程是否正常退出,若正常退出,返回非0值。
WEXITSTATUS(status);//经过上一个宏判断后,使用该宏取出进程结束时候的结束码。
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/wait.h>
int main()
{
pid_t pid1,pid2;
int statu,m;
pid1 = fork();
if(0 == pid1)
{
printf("This is son! it's PID %d\n",getpid());
exit(1); //结束进程
}
if(0 < pid1)
{
pid2 = wait(&statu); //父进程等待子进程执行,子进程结束后的状态保存在statu里
m = WEXITSTATUS(statu); //如果子进程正常结束,为非0值。
WEXITSTATUS(statu); //取得子进程退出状态
printf("This is father!\n");
printf("m = %d\n",m);
printf("son status is %d\n",statu);
printf("son PID is %d\n",pid2);
}
return 0;
}
执行结果如下:
waitpid函数和wait的不同之处在于,waitpid函数多了两个参数,使我们能控制等待的进程,以及是否等待。
函数原型:pid_t waitpid(pid_t pid, int *status, int options);
函数功能:pid是控制等待的进程,status和wait中的意义一样,options参数一般用了控制父进程是否等待。
pid = -1 :等待任何子进程,相当于wait函数。
pid = 0:等待同一个进程组中的任何子进程(如果子进程已经加入了别的进程组,waitpid 不会等待它)。
pid > 0:等待任何子进程识别码为pid的子进程
pid < -1:等待进程组识别码为pid绝对值的任何子进程options:它的取值组合由系统预定义的。可以为0和一些宏的或。当他为0时,和wait()一样,阻塞父进程,等待子进程退出。当他取值为WNOHANG时,如果没有已经结束的子进程则马上返回,不等待子进程。最常用的就是这两个。
将上面代码中的
pid2 = wait(&statu);
替换为下面这句代码
pid2 = waitpid(pid1,&statu,WNOHANG);
运行结果将会发生变化:
显而易见,父进程没有等待子进程,直接执行,打印父进程中代码,由于未初始化statu的缘故,打印一个随机值。m是从statu中提取出来的,也是随机值。设置了选项 WNOHANG,而调用中 waitpid() 发现没有已退出的子进程可等待,返回0。所以取到的子进程的PID是0。