zoukankan      html  css  js  c++  java
  • Linux僵尸进程

    僵尸进程就是已经结束的进程(几乎不占计算机资源),但是它并没有从进程列表中删除。僵尸进程太多会导致操作系统的进程数目过多,从而占满了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
    ",getpid());
            exit(1);                    //结束进程
        }
        if(0 < pid1)
        {
            pid2 = wait(&statu);        //父进程等待子进程执行,子进程结束后的状态保存在statu里
            m = WEXITSTATUS(statu);     //如果子进程正常结束,为非0值。
            WEXITSTATUS(statu);         //取得子进程退出状态
            printf("This is father!
    ");
            printf("m = %d
    ",m);
            printf("son status is %d
    ",statu);
            printf("son PID is %d
    ",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。

  • 相关阅读:
    一文了解快排的各种形式和坑
    逆序对的数量
    __type_traits 技法
    Traits 编程技法
    一个简单的内存管理器
    「ZooKeeper」概述
    「MySQL」explain
    「MySQL」InnoDB加锁情况
    「MySQL45讲」个人总结
    「MySQL45讲」1-45讲
  • 原文地址:https://www.cnblogs.com/zy666/p/10504277.html
Copyright © 2011-2022 走看看