僵尸进程详解

僵尸进程简介

“僵尸”进程是什么?通常情况下,造成僵尸进程的成因是因为该进程本应该已经执行完毕,但是该进程的父进程却无法完整的将该进程结束掉,而造成该进程一直存在于内存中。

那么如何查看一个进程是否为僵尸进程呢?

ps:将某个时间点的进程运行状态选取下来

ps aux    //查看系统所有的进程数据
-A:所有的进程均显示出来
-a:不与terminal有关的所有进程
-u:有效用户相关的进程
-x:通常与a一起使用,可以列出较完整的信息
-l:较长、较详细地将该PID的信息列出

如图:
这里写图片描述

如上图

1、 F:进程标志,说明这个进程的权限,常见号码有:
若为4则代表权限为root
若为1则代表仅可被复制(fork),而无法实际执行(exec)
2、S:代表进程的状态
R ( Running):正在运行的进程
S(Sleeping):正在睡眠的进程,但可被唤醒
D:不可被唤醒的睡眠状态,一般都是在进行数据的I/O
T:停止状态
Z(Zombie):僵尸状态,进程已经终止但却无法被删除至内存外

也可以通过top指令来查看是否存在僵尸进程

top:动态查看进程的变化

top:参数
-d:后面接秒数,表示显示整个进程界面更新的秒数,如top -d 5
-b:以批次的方式执行top
-n:与-b搭配,意思是需要几次top输出的结果
-p:指定某个特定的PID进行检测

如图:
这里写图片描述

光标处即目前的僵尸进程数量。


举例

这是一个维持30秒的僵尸进程

#include<stdlib.h>
#include<stdio.h>

int main(int argc,char** argv[])
{
    int id = fork();

    if(id>0)
    {
        printf("Parent is sleeping\n");
        sleep(30);
    }
    if(id == 0)
    printf("Child process is done\n");

    exit(EXIT_SUCCESS);
}

通俗一点,僵尸进程就是指子进程先于父进程挂掉 但是父进程并没有正确回收子进程的资源而已。


进程的管理

当你获知它是一个僵尸进程后,那么你该如何干掉它呢,那么首先就得了解一下进程的管理。

程序之间的相互管理,是通过给予一个信号来进行管理的

查看信号(signal):
1、man 7 signal
2、kill -l

这里写图片描述

通常情况下,我们只需记住几个特别重要的信号即可。
1:启动被终止的进程,可让该PID重新读取自己的配置文件
9:强制中段一个进程,如果该进程运行到一半(如vim)会产生.filename.swap的半产品文件
15:正常结束一个进程
18:继续运行该进程
19:暂停一个进程

例如,强行杀掉一个进程:
这里写图片描述
在实际情况下,如果我们有时无法直接杀掉一个僵尸进程,可以找到其父进程将其杀掉,从而干掉该僵尸进程。


总的来说,当系统不稳定时,或者代码不够完善,亦或是用户操作不当都可能产生僵尸进程,而僵尸进程是1个早已死亡的进程,但在进程表(processs table)中仍占了1个位置(slot)。由于进程表的容量是有限的,所以就占用了内存资源,影响系统性能。


补充:

如何防止僵尸进程的产生

如上所述,当子进程先行退出,且父进程不对其做回收的话,会产生僵尸进程,我们可以通过的进程等待,来让父进程等待子进程退出然后进行回收。

具体的进程等待这里就不赘述了。可以参考进程控制中的进程等待部分。

这里主要是为了提一下后续学到了信号部分的知识,当子进程退出时操作系统会发送SIGCHLD信号,而该信号的默认处理动作是忽略,父进程可以自定义SIGCHLD信号的处理函数,这样父进程就可以处理自己的事情,而不必关心子进程的退出,在父进程的处理函数中调用wait清理子进程即可。

看如下代码:

#include <stdio.h>
#include<signal.h>
#include<sys/types.h>
#include<wait.h>
#include<unistd.h>
#include<stdlib.h>

//自定义SIGCHLD信号的处理函数,对子进程进程wait
void Myhandler(sigset_t signal)
{
    printf("wait\n");
    wait(NULL);
}

int main()
{
    //当收到SIGCHLD信号时,执行自定义的处理函数
    signal(SIGCHLD,Myhandler);
    pid_t id = fork();
    if(id > 0){
        //father
        while(1){
            printf("father doing some thing!\n");
            sleep(1);
        }
    }else if(id == 0){
        //child
        sleep(3);
        exit(1);
    }else{
        perror("fork");
        return 1;
    }
    return 0;
}

这段代码子进程先退出,但是子进程退出后会给父进程发送一个SIGCHLD信号,在我们的程序中,当父进程收到一个SIGCHLD信号后,执行自定义的处理函数,在处理函数中进行wait。这样就避免了僵尸进程的产生。
如下运行结果
这里写图片描述

但是如果创建20个子进程呢?还能避免所有的僵尸进程嘛?
看如下代码:

void Myhandler(sigset_t signal)
{
    printf("wait\n");
    wait(NULL);
}

int main()
{
    signal(SIGCHLD,Myhandler);
    pid_t cid;
    int i = 0;
    for(;i < 20;++i)
    {
        cid = fork();
        if(cid == 0)
        exit(0);
    }
    if(cid > 0)
    {
        while(1)
        {
            printf("father doing some thing!\n");
            sleep(1);
        }
    }
    else if(cid == 0)
    {
        sleep(3);
    }
    return 0;
}

这里写图片描述
可以看到,只wait了15次,这样意味着产生了5个僵尸进程,来查看一下系统中是否真的出现了5个僵尸进程。
这里写图片描述
解释下为什么会出现僵尸进程,因为操作系统在接受到一个信号时,在执行处理函数时,就会屏蔽该信号,所以当有多个子进程同时退出发送信号时,操作系统就收到一个信号,所以就造成僵尸进程的出现。

基于这个问题,可以采用两种发生来解决。

一:一次处理函数wait多个子进程

这里用到waitpid这个函数,函数返回值正常返回子进程的PID,如果设置了第三个参数,即WNOHANG表示非阻塞式等待时,如果返回值为0则表示没有需要回收的子进程了,基于这一点,我们可以在处理函数中循环调用waitpid函数。
代码如下:

void Myhandler(sigset_t signal)
{
    pid_t id;
    while((id = waitpid(-1,NULL,WNOHANG) > 0)){
        printf("child wait success:%d\n",id);
    }
    printf("child is quit!\n");
}

int main()
{
    signal(SIGCHLD,Myhandler);
    pid_t cid;
    int i = 0;
    for(;i < 20;++i)
    {
        cid = fork();
        if(cid == 0)
        exit(0);
    }
    if(cid > 0)
    {
        while(1)
        {
            printf("father doing some thing!\n");
            sleep(1);
        }
    }
    else if(cid == 0)
    {
        sleep(3);
    }
    return 0;
}

这里写图片描述
这里写图片描述
由于篇幅问题,有一点没截上,可以看到子进程全部被回收了,系统中并没有产生僵尸进程。

二:使用sigaction函数

sigaction是后来取代signal函数出现的,以为它的接口更丰富,还有另外一种方法就是使用sigaction将函数将SIGCHLD的默认处理函数设置为SIG_IGN,这样fork出来的子进程在终止时会自己清理掉,不会产生僵尸进程,也不会通知父进程。
代码如下:

int main()
{
    struct sigaction new,old;
    new.sa_handler = SIG_IGN;
    sigemptyset(&new.sa_mask);
    new.sa_flags = 0;
    sigaction(SIGCHLD,&new,&old);
    pid_t cid;
    int i = 0;
    for(;i < 20;++i)
    {
        cid = fork();
        if(cid == 0)
        exit(0);
    }
    if(cid > 0)
    {
        while(1)
        {
            printf("father doing some thing!\n");
            sleep(1);
        }
    }
    else if(cid == 0)
    {
        sleep(3);
    }
    sigaction(SIGCHLD,&old,NULL);
    return 0;
}

就先补充到这了,后续有新的理解,会继续更新。

  • 15
    点赞
  • 49
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值