孤儿进程僵尸进程及其回收

Posted clno1

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了孤儿进程僵尸进程及其回收相关的知识,希望对你有一定的参考价值。

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

 

什么是孤儿进程?

孤儿进程: 父进程先于子进程结束,则子进程成为孤儿进程,子进程的父进程成为 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信号回收子进程

 

以上是关于孤儿进程僵尸进程及其回收的主要内容,如果未能解决你的问题,请参考以下文章

僵尸进程和孤儿进程----概念

僵尸 孤儿 守护 进程的理解

进程——wait与waitpid僵尸进程与孤儿进程

进程控制孤儿进程和僵尸进程

linux的僵尸进程和孤儿进程

孤儿进程和僵尸进程