前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Linux僵尸进程

Linux僵尸进程

作者头像
zy010101
发布2019-05-25 19:56:33
4.4K0
发布2019-05-25 19:56:33
举报
文章被收录于专栏:程序员程序员

版权声明:本文为博主原创文章,转载请注明博客地址: https://cloud.tencent.com/developer/article/1433350

僵尸进程就是已经结束的进程(几乎不占计算机资源),但是它并没有从进程列表中删除。僵尸进程太多会导致操作系统的进程数目过多,从而占满了OS的进程表。进而导致无法创建新进程,致使OS崩溃。

僵尸进程几乎不占资源,它没有可执行代码,也不能被调度,但是它占据着进程表中的一个位置,记载这该进程的PCB信息。它需要等待他的父进程来终结它。一旦它的父进程是一个循环,不会结束(父进程不去调用wait函数或者waitpid函数)。那么子进程将会一直保持僵尸状态。那么它将一直占用进程号,系统就没法回收利用。

在Linux下使用top命令可以产看当前进程数目,以及进程的状态。例如:

可以看到我的系统暂时并没有僵尸进程(zombie) 。挂起的进程倒是一大堆。

僵尸进程产生的原因:每个Linux进程在进程表中都有一个进入点,内核执行该进程时,使用到的一切信息都存入在进程点。我们可以使用ps命令来查看进程状态。当一个父进程以fork()系统调用建立一个新的子进程后,核心进程就会在进程表中给这个子进程分配一个进入点,然后将相关信息存储在该进入点所对应的进程表内。这些信息中有一项是其父进程的识别码。而当这个子进程结束的时候(调用exit命令结束),其实他并没有真正的被销毁,而是留下一个僵尸进程的。此时原来进程表中的数据会被该进程的退出码(exit code)、执行时所用的CPU时间等数据所取代,这些数据会一直保留到系统将它传递给它的父进程为止。

我们用下面的代码来产生僵尸进程

代码语言:javascript
复制
#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);//经过上一个宏判断后,使用该宏取出进程结束时候的结束码。

代码语言:javascript
复制
#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时,如果没有已经结束的子进程则马上返回,不等待子进程。最常用的就是这两个。

将上面代码中的

代码语言:javascript
复制
pid2 = wait(&statu);

替换为下面这句代码

代码语言:javascript
复制
pid2 = waitpid(pid1,&statu,WNOHANG);

运行结果将会发生变化:

显而易见,父进程没有等待子进程,直接执行,打印父进程中代码,由于未初始化statu的缘故,打印一个随机值。m是从statu中提取出来的,也是随机值。设置了选项 WNOHANG,而调用中 waitpid() 发现没有已退出的子进程可等待,返回0。所以取到的子进程的PID是0。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2018年11月04日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档