僵尸进程

引言:
在Linux/Unix系统编程时,会经常遇到僵尸进程(Zombie)这个概念。类似电影中的僵尸一样,僵尸进程指的是那些已经运行结束、却仍然占着一些内存资源,没有被彻底清理的进程。
一个进程结束之后,内核会释放该进程的资源,包括打开的文件、占用的内存的高等,此后它将成为一个僵尸进程,在它的父进程没有wait/waipid它之前,它将一直保持这个状态。它仍然保留一定的信息(包括PID、退出状态、运行时间等)。僵尸进程存在的意义是让父进程获取它的退出信息。

1.僵尸进程产生的原因

我们知道在Linux/Unix中每个进程(除init)都是通过其父进程创建的,然后子进程再创建新的子进程,如此周而复始。子进程的结束和父进程的运行是一个异步过程,也就是说,父进程永远无法知道子进程何时结束。当一个进程结束后,在没有被wait/waitpid的情况下,它将成为一个僵尸进程,在这种状态下,它通过两种途径被彻底杀死。1.父进程调用wait()/waitpid()获取它的退出信息后,它被彻底杀死。 2.它的父进程在结束,它作为一个孤儿进程被init进程收养(即init成为它的父进程),然后init进程再将它彻底杀死。不管通过哪种途径,一个进程要彻底从你的系统中被清除,都需要其父进程等待(wait/waitpid)它。

如上图中有个状态为Z的进程,表明该进程是僵尸进程。


2.模拟创建一个僵尸进程

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

int main()
{
    pid_t pid;
    pid = fork();//创建一个子进程

    if(pid < 0)
    {
        perror("fork()");
        exit(0);
    }

    //通过返回的PID做不同的事情
    if(0 == pid)
    {
        printf("I am a child process.\n");  
        sleep(2);
    }

    else
    {
        printf("I am a father process.\n");
        sleep(5);
    }

    //输出进程的信息
    system("ps -o pid,ppid,stat,tty,command | grep zombie");
    printf("exit!\n");

    return 0;
}

fork()函数通过系统调用创建一个与原来进程几乎完全相同的进程。调用fork()之后,操作系统会复制一个全新的task_struct结构体,这个结构体除了ID号不一样外,其余的都完全一样。这意味着,两个进程的内存空间也是映射到相同的地址(也就是说这两个进程共享同一片内存空间)。fork()的进程采用copy on write的机制,当某一个进程试图去修改其共享的数据时,操作系统会产生”缺页中断”为该进程分配新的内存空间。

pid_t fork()函数与我们平常的函数不同之处在于,它仅被调用一次却能返回两个值,它有三种返回值:
1. 在父进程中fork()返回子进程的PID
2. 在子进程中fork()返回0。
3. 如果出现错误,则返回一个负值。

fork()函数执行完毕后,如果创建新进程成功,则出现两个进程,一个是子进程,一个是父进程。在子进程中,fork()函数返回0,在父进程中,
fork()返回新创建子进程的进程ID。我们可以通过fork()返回的值来判断当前进程是子进程还是父进程。

所以在调用fork()函数之前,只有一个进程执行这段代码,在这个函数之后,不出意外的话就有两个进程在同时执行了。
该代码的执行结果如下:

我们先fork()一个子进程,然后父进程和子进程同时执行,状态都为sleep,紧接着让父进程睡眠5s后,此时子进程已经执行结束,再次查看他们的状态,发现子进程状态已经成为Z了,即此时子进程是一个僵尸进程。


3.僵尸进程有什么危害

僵尸进程的最显著特点是“占着茅坑不拉屎”,显然,它主要的危害就是占用着系统资源,却什么也不干,有点像我们在c中常犯的错误——内存泄漏。操作系统本身的进程当然不会犯这种错误了,如果由于我们人为的原因,产生大量的僵尸进程,而且并没有处理它们,那么这会严重影响操作系统的性能。


4.如何处理僵尸进程

我觉得更准确的来说,应该是如何处理僵尸进程。在Linux/Unix系统中沦为僵尸进程是一个进程发展的必经阶段,我们无法避免,就如同动物在死亡时其尸体不会直接凭空消失一样,我们只能想办法在一个进程死亡后,迅速给它收尸,不让它长时间的“占着茅坑”。

我们可以通过下面几个方法处理僵尸进程:

  1. 父进程调用wait()/waitpid()函数,获取完退出信息后,子进程被彻底清理。
  2. 让该进程成为孤儿进程,init收养它,然后交给init处理它。
  3. 调用fork()两次。

对于方法1

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

int main()
{
    pid_t pid;
    pid = fork();//创建一个子进程

    if(pid < 0)
    {
        perror("fork()");
        exit(0);
    }

    //通过返回的PID做不同的事情
    if(0 == pid)
    {
        printf("I am a child process.\n");  
        sleep(2);
    }

    else
    {
        printf("I am a father process.\n");
        sleep(5);
        wait(0);  //重点!!!父进程wait子进程。
    }

    //输出进程的信息
    system("ps -o pid,ppid,stat,tty,command | grep zombie");
    printf("exit!\n");

    return 0;
}

函数:pid_t wait (int * status);
说明:wait()会暂时停止目前进程的执行, 直到有信号来到或子进程结束. 如果在调用wait()时子进程已经结束, 则wait()会立即返回子进程结束状态值. 子进程的结束状态值会由参数status 返回, 而子进程的进程识别码也会一起返回. 如果不在意结束状态值, 则参数 status 可以设成NULL. 子进程的结束状态值请参考waitpid().

当子进程结束后,我们在父进程成中调用wait()函数,处理子进程遗留下的问题。
它的运行结果如下:

显然此时子进程已经被彻底杀死。

对于第二种情况,我们修改代码如下:

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

int main()
{
    pid_t pid;
    pid = fork();//创建一个子进程

    if(pid < 0)
    {
        perror("fork()");
        exit(0);
    }

    //通过返回的PID做不同的事情
    if(0 == pid)
    {
        printf("I am a child process.\n");  
        sleep(5);
    }

    else
    {
        printf("I am a father process.\n");
    }

    //输出进程的信息
    system("ps -o pid,ppid,stat,tty,command | grep zombie");
    printf("exit!\n");

    return 0;
}

先让父进程退出,然后子进程将作为孤儿进程被init(ID为1的进程)收养。
运行结果如下:

我们发现父进程比子进程早退出后,子进程已经被init进程收养(它的PPID为1),那么随后子进程的收尾工作将交给init进程完成。

第三种fork()两次的意义在于,我们在父进程的孙子进程上工作,当工作完毕之后,将父进程的子进程杀死,这样的话,孙子进程就作为孤儿进程被init收养,与2的原理类似。


参考内容:

  1. fork(). http://blog.csdn.net/hyfcomeon/article/details/906023
  2. 缺页中断. http://baike.baidu.com/item/%E7%BC%BA%E9%A1%B5%E4%B8%AD%E6%96%AD
  3. wait(). http://c.biancheng.net/cpp/html/289.html
  4. 孤儿进程:http://baike.baidu.com/link?url=1gwzcjNRTO0OSiOhmUhJzDl_6Oxkk040fpVP3R29Re5VsuyW9CvArYZj85D78R6B-xGzY1HtRBbICAl5RqMgSqnxKBza_ytnHAmG5-D175hGiBtouKCopQdoidxUIIIR

【作者:果冻 http://blog.csdn.net/jelly_9

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值