Linux僵尸进程

版权声明:本文为博主原创文章,转载请注明博客地址: https://blog.csdn.net/zy010101/article/details/83715103

僵尸进程就是已经结束的进程(几乎不占计算机资源),但是它并没有从进程列表中删除。僵尸进程太多会导致操作系统的进程数目过多,从而占满了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进程的数量在一直增长。如下图所示:

如何避免僵尸进程:

  1. 可以在父进程中通过调用wait()和waitpid函数等待子进程结束,但是这会导致父进程挂起。
  2. 父进程不能挂起,父进程要做的工作很多,很忙。那么可以使用signal函数为SIGCHLD安装handler,因为子进程结束后,父进程会收到该信号,可以在headler中使用wait函数来回收子进程。
  3. 如果父进程不关心子进程什么时候结束(比如fork后使用了execl函数启动了另外一个可执行程序),那么可以使用single(SIGCHLD,SIG_IGN)通知内核来回收子进程。
  4. fork两次,首先父进程fork一个子进程,然后继续工作,子进程fork一个孙子进程后退出,那么孙子进程将会变成孤儿进程(因为他父亲死了,这就是孤儿),从而被init进程接管。但是子进程的回收仍旧需要父进程来做,好处是不用使用wait()来挂起了,父进程可以忙自己的。

使用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。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

扫码关注云+社区

领取腾讯云代金券