孤儿进程和僵尸进程的概念及进程回收(wait函数及waitpid函数)

一.孤儿进程
孤儿进程可以理解为一个子进程的父进程英年早逝(父进程先于子进程退出),就将这样的一个进程称为孤儿进程,在linux操作系统上。孤儿进程被init进程收养,此时孤儿进程的ppid==1,即init进程的pid == 1。也就是说init进程变成孤儿进程的父进程(干爹)。

下面举例说明什么是孤儿进程:

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

int main()
{
    pid_t pid = fork();
    if(pid == 0)//子进程
    {
        sleep(1);//目的是让父进程先于子进程执行完毕
        printf("son process start\n");
        printf("my pid is %d,my ppid is %d\n",getpid(),getppid());
        printf("son process end\n");
    }
    else
    {
        printf("father process start\n");
        printf("my pid is %d\n",getpid());  
        printf("father process end\n");
    }
    return  0;
}

这里写图片描述
从执行结果来看,此时由pid == 3363父进程创建的子进程,其输出的父进程pid == 1,说明当其为孤儿进程时被init进程回收。

我们查证一下init进程的pid是否为1
这里写图片描述

操作系统为什么要给孤儿进程分配init进程收养孤儿进程?
其目的只有一个,就是为了释放系统资源
进程结束之后,能够释放用户区空间。但不能释放pcb(进程控制块),即内核资源。pcb必须由子进程的父进程进行释放。

二.僵尸进程
(1)父进程成功创建子进程,且子进程先于父进程退出。
(2)子进程需要父进程回收其所占资源,释放pcb。但是父进程不作为,不去释放已经退出子进程的pcb。
(3)这样的子进程变为僵尸进程。
(4)僵尸进程是一个已经死掉了的进程。

下面举例验证什么是僵尸进程:

#include<unistd.h>
#include<sys/types.h>

int main()
{
    pid_t pid = fork();//创建子进程
    if(pid == 0)
    {
        printf("my pid is %d\n",getpid());
        printf("my ppid is %d\n",getppid());
    }
    else
    {
        while(1)
        {
            ;//死循环,阻塞  不处理已经退出的子进程,致使子进程称为僵尸进程
        }
    }
    return 0;
}

这里写图片描述

从图可以看出,子进程执行完毕。父进程处于阻塞状态(陷入死循环)。
再开启另外一个bash窗口,使用ps -aux | grep 3749命令查看僵尸进程的状态。
这里写图片描述
defunct是已死的,僵尸的意思。
也就是说pid值为3749的进程为僵尸进程,是因为其父亲不对已经结束的子进程做进程回收。但是已经结束的子进程的资源必须由其父进程进行回收,因而产生了僵尸进程。

试想一下,如果有大量的僵尸进程驻在系统之中,必然消耗大量的系统资源。但是系统资源是有限的,因此当僵尸进程达到一定数目时,系统因缺乏资源而导致奔溃。所以在实际编程中,避免和防范僵尸进程的产生显得尤为重要。

三.进程回收
(1)回收僵尸进程的资源,一种比较暴力的做法是将其父进程杀死,那么子进程资源也将被回收。但是这种做法在大多数情况下都是不可取的,如父进程是一个服务器程序,如果为了回收其子进程的资源,而杀死服务器程序,那么将导致整个服务器崩溃,得不偿失。显然这种回收进程的方式是不可取的,但其也有一定的存在意义。那么有没有更好的解决方案呢,且看下边的两种方式。
注:kipp -9 + (父进程的pid)是第一种回收子进程资源的方式。

扫描二维码关注公众号,回复: 1672453 查看本文章

(2)wait系统调用函数
所需头文件:

#include<sys/types.h>
#include<sys/wait.h>

函数原型: pid_t wait(int* status),是一个阻塞函数。

返回值:如果为-1,回收失败,已经没有子进程可以回收了。
如果 > 0,返回值为子进程对应的pid。

参数:子进程的退出状态,是一个传出参数。
判断子进程是如何死的 (1)正常退出 (2)被信号杀死

(1)WIFEXITED(status):为非0,进程正常结束。
WEXITSTATUS(status):如果上宏为真,使用此宏,获取进程的退出状态(exit/return的参数)。
(2)WIFEXITEDWIFSIGNALED(status):为非0,进程异常终止。
WTERMSIG(status):如上宏为真,使用此宏,获取使进程终止的那个信号的编号。

注意:pid_t类型即为int类型。调用一次,只能回收一个子进程,如果回收多个子进程,就需要多次调用wait函数。
函数功能:
(1)阻塞并等待子进程退出。
(2)回收子进程残留资源。
(3)获取子进程结束状态(退出原因)。

实例一:不关心子进程的退出状态

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

int main()
{
    pid_t pid = fork();
    if(0 == pid)
    {
        printf("my pid is %d\n",getpid());
    }
    else
    {
        sleep(1);//目的是让子进程先于父进程结束
        /*wait函数是阻塞函数,会等到子进程结束回收资源*/
        pid_t dpid = wait(NULL);//对子进程的退出状态不关心
        printf("the died son process pid is %d\n",dpid);
    }
    return 0;
}

执行结果:
这里写图片描述

实例二:利用参数int* status获取子进程的退出状态

1.获取正常退出的状态

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

int main()
{
    pid_t pid = fork();
    if(0 == pid)
    {
        printf("my pid is %d\n",getpid());
    }
    else
    {
        sleep(1);
        int status = 0;//初始化为0
        pid_t dpid = wait(&status);
        if(WIFEXITED(status))
        {
            printf("exit value:%d\n",WEXITSTATUS(status));
        }
        printf("the died son process pid is %d\n",dpid);
    }
    return 10;//返回值为10  
}

执行结果:
这里写图片描述
2.获取异常退出的状态。

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

int main()
{
    pid_t pd = fork();
    if(pid == 0)
    {
        while(1)
        {
            printf("my pid is %d\n",getpid());
            sleep(2);//控制执行速度不要太快
        }
        else
        {
            sleep(1);
            int status = 0;//初始化为0
            wait(&status);
            if(WIFSIGNALED(status))
            {
                printf("exit by signal : %d\n",WTEEMSIG(status));
            }
        }
    }
    return 0;
}

这里写图片描述
查找子进程的pid值,并杀死。
这里写图片描述

再次查看执行结果
这里写图片描述

使用kill -l命令可以擦看所有信号,其中编号为9的信号是SIGKILL
这里写图片描述

子进程被编号为9的信号杀死。
(3)waitpid系统调用函数
函数功能:和wait函数相同。
所需头文件

#include<sys/types.h>
#include<sys/wait.h>

函数原型: pid_t waitpid(pid_t pid,int* status,int options)
返回值:
(1)>0:返回清理掉的子进程的PID。
(2)-1:无子进程可以回收。
(3)=0:参数3为WNOHANG,且子进程正在运行。
参数:
1.pid 可以有选择的回收
(1)pid:pid == -1,等待任意子进程,与wait等效。
(2)pid > 0,等待其进程ID与pid相等的子进程。
(3)pid == 0,等待其组ID等于调用进程的组ID的任一子进程。
(4)pid < -1,等待其组ID等于pid的绝对值的任一子进程。

2.status:子进程的退出状态,用法同wait函数。
3.options:设置为WNOHANG,函数非阻塞。设置为0,阻塞函数。

猜你喜欢

转载自blog.csdn.net/asjbfjsb/article/details/80457737