zoukankan      html  css  js  c++  java
  • 孤儿进程僵尸进程及其回收

    孤儿进程僵尸进程及其回收是进程的经典知识了。

    什么是孤儿进程?

    孤儿进程: 父进程先于子进程结束,则子进程成为孤儿进程,子进程的父进程成为 init进程,称为 init 进程领养孤儿进程。

    什么是僵尸进程?

    僵尸进程: 进程终止,父进程尚未回收,子进程残留资源(PCB)存放于内核中,变成僵尸(Zombie)进程。

    特别注意,僵尸进程是不能使用 kill 命令清除掉的。因为 kill 命令只是用来终止进程的,而僵尸进程已经终止。

    孤儿进程有init进程回收,但是僵尸进程要等父进程来回收,如果父进程不回收,僵尸进程会已经死亡得不到回收却占用进程号,如果大量的产生僵尸进程,将因为没有可用的进程号而导致系统不能产生新的进程. 此即为僵尸进程的危害,应当避免。

    相关函数

    一个进程在终止时会关闭所有文件描述符,释放在用户空间分配的内存,但它的 PCB还保留着,内核在其中保存了一些信息:如果是正常终止则保存着退出状态,如果是异常终止则保存着导致该进程终止的信号是哪个。

    这个进程的父进程可以调用 wait 或 waitpid 获取这些信息,然后彻底清除掉这个进程。我们知道一个进程的退出状态可以在 Shell 中用特殊变量$?查看,因为 Shell 是它的父进程,当它终止时 Shell 调用 wait 或 waitpid 得到它的退出状态同时彻底清除掉这个进程。

    wait 函数

    pid_t wait(int *status); 

    父进程调用 wait 函数可以回收子进程终止信息。该函数有三个功能:

      ① 阻塞等待子进程退出  ② 回收子进程残留资源  ③ 获取子进程结束状态(退出原因)。

    函数返回值:成功:清理掉的子进程 ID;失败:-1 (没有子进程)

    当进程终止时,操作系统的隐式回收机制会:1.关闭所有文件描述符 2. 释放用户空间分配的内存。内核的 PCB 仍存在。其中保存该进程的退出状态。(正常终止→退出值;异常终止→终止信号)

    status判断终止原因

    可使用 wait 函数传出参数 status 来保存进程的退出状态。借助宏函数来进一步判断进
    程终止的具体原因。宏函数可分为如下三组:
    1. WIFEXITED(status) 为非 0 → 进程正常结束
        WEXITSTATUS(status) 如上宏为真,使用此宏 → 获取进程退出状态 (exit 的参数)
    2. WIFSIGNALED(status) 为非 0 → 进程异常终止
        WTERMSIG(status) 如上宏为真,使用此宏 → 取得使进程终止的那个信号的编号。
    *3. WIFSTOPPED(status) 为非 0 → 进程处于暂停状态
        WSTOPSIG(status) 如上宏为真,使用此宏 → 取得使进程暂停的那个信号的编号。
        WIFCONTINUED(status) 为真 → 进程暂停后已经继续运行

     1 #include<stdio.h>
     2 #include<stdlib.h>
     3 #include<unistd.h>
     4 #include<sys/wait.h>
     5 
     6 int main()
     7 {
     8     pid_t pid,wpid;
     9     int status;
    10 
    11     pid=fork();
    12     if (pid==0) {    //子进程
    13         printf("i am child, pid is %d, sleep 10s ",pid);
    14         sleep(20);
    15         printf("i am diying");
    16         return 73;
    17     } else if (pid>0) {    //父进程
    18         //wait函数会阻塞,如果没有一个子进程死亡
    19         //wpid=wait(NULL);    //不关心子进程如何死亡
    20         wpid=wait(&status);    //关心子进程死亡,写到status里面
    21         if (wpid==-1) {
    22             perror("wait error");
    23             exit(1);
    24         }
    25 
    26         if (WIFEXITED(status))        //为真,子进程正常终止
    27             printf("child exit with %d
    ",WEXITSTATUS(status));
    28         
    29         if (WIFSIGNALED(status))    //为假,子进程被信号终止
    30                printf("child kill with signal %d
    ",WTERMSIG(status));
    31 
    32         printf("wait successful, %d died",pid);           
    33 
    34     } else {
    35         perror("fork error");    //fork出错了
    36         return 1;
    37     }
    38     return 0;
    39 }
    wait回收子进程

    waitpid 函数

    pid_t waitpid(pid_t pid, int *status, in options);   作用同 wait,但可指定 pid 进程清理,可以不阻塞。 

    特殊参数和返回情况:

    参数 pid:> 0 回收指定 ID 的子进程  -1 回 回于 收任意子进程(相当于 wait )  0 回收和当前调用 waitpid 一个组的所有子进程  < -1 回收指定进程组内的任意子进程

    参数status:和wait函数的status一样,判断进程终止原因

    参options:    0 :(相当于wait)阻塞回收    WBNIOHANG非阻塞回收(轮询)

    返回值:成功:返回清理掉的子进程 ID;失败:-1(无子进程)

        特殊的返回 0:参 3 为 WNOHANG,且子进程正在运行。


    注意:次 一次 wait 或 或 waitpid 调用只能清理一个子进程,清理多个子进程应使用循环while。

     1 #include<stdio.h>
     2 #include<stdlib.h>
     3 #include<string.h>
     4 #include<unistd.h>
     5 #include<sys/wait.h>
     6 #include<pthread.h>
     7 
     8 int main(int argc,char *argv[])
     9 {
    10     int i;
    11     pid_t pid,wpid;
    12 
    13     for (i=0;i<5;i++) {
    14         pid=fork();
    15         if (pid==0) break;
    16     }
    17 
    18     if (i==5) {    //父进程
    19         
    20         //这里不断while循环等待死亡的子进程
    21         while ((wpid=waitpid(-1,NULL,WNOHANG))!=-1) {
    22             if (wpid>0) {    //回收成功
    23                 printf("wait child %d 
    ",wpid);
    24             } else if (wpid==0) {    //回收失败
    25                 sleep(1);    //sleep一秒之后再尝试回收
    26                 continue;
    27             }
    28         }
    29     } else {    //5个子进程
    30         sleep(i);    //子进程sleep以一下
    31         printf("I'm child %d ,i am dying",i);
    32     }
    33     return 0;
    34 }
    waitpid回收所以子进程

     

    更加美妙的回收方式是使用SIGCHLD信号回收子进程,这样父进程不阻塞可以干自己的事情:https://www.cnblogs.com/clno1/p/12941316.html

     1 #include<stdio.h>
     2 #include<stdlib.h>
     3 #include<string.h>
     4 #include<unistd.h>
     5 #include<signal.h>
     6 #include<sys/wait.h>
     7 #include<errno.h>
     8 #include<pthread.h>
     9 
    10 void sys_err(const char *str) {
    11     perror(str);
    12     exit(1);
    13 }
    14 
    15 void catch_child(int signo) {
    16     pid_t wpid;
    17     int status;
    18 
    19     //这里的while非常重要,当接受到SIGCHLD信号时候,有多个子进程同时死亡,这时候就需要while来把这段时间的死亡子全部回收
    20     while ((wpid=waitpid(-1,&status,0))!=-1) {
    21         if (WIFEXITED(status))
    22             printf("-------------catch child id %d, ret=%d 
    ",wpid,WEXITSTATUS(status));
    23     }
    24     return;
    25 }
    26 
    27 int main(int argc,char *argv[])
    28 {
    29     pid_t pid;
    30     int i;
    31 
    32     for (i=0;i<15;i++)
    33         if ((pid=fork())==0)    //创建15个子进程
    34             break;
    35 
    36     if (i==15) {    //父进程
    37         
    38         //信号结构体,三个参数重要
    39         struct sigaction act;    
    40 
    41         act.sa_handler=catch_child;    //注册函数
    42         sigemptyset(&act.sa_mask);    //在执行期间,sa_mask会替换原mask
    43         act.sa_flags=0;        //设为0,在该信号处理函数期间,再次收到同样信号就屏蔽
    44 
    45         sigaction(SIGCHLD,&act,NULL);
    46 
    47         printf("I'm parent, pid=%d 
    ",getpid());
    48 
    49         while(1);
    50 
    51     } else {    //子进程
    52         printf("I'm child pid= %d
    ",getpid());
    53         return i;
    54     }
    55     return 0;
    56 }
    SIGCHLD信号回收子进程
  • 相关阅读:
    台州 OJ 3847 Mowing the Lawn 线性DP 单调队列
    洛谷 OJ P1417 烹调方案 01背包
    快速幂取模
    台州 OJ 2649 More is better 并查集
    UVa 1640
    UVa 11971
    UVa 10900
    UVa 11346
    UVa 10288
    UVa 1639
  • 原文地址:https://www.cnblogs.com/clno1/p/12937547.html
Copyright © 2011-2022 走看看