进程学习:处理僵尸进程的两种方式

上篇讲到僵尸进程产生的原因及危害,并且简要的提到了避免产生僵尸进程的两种方法:
一、让父进程先退出,形成孤儿进程,最终由1号进程(init进程)来回收子进程系统资源;
二、父进程调用wait、waitpid函数,以阻塞或非阻塞轮训的方式来等待子进程退出,并回收子进程的系统资源;
本篇将具体来讲解一下,两种解决方案的具体实施过程。


一、避免僵尸进程之 ✈✈✈✈ 形成孤儿进程

《Unix 环境高级编程》8.6节

#include 
#include 
#include 
#include 

int main(int argc, const char *argv[])
{
    pid_t  pid;

    //创建第一个子进程
    if ( (pid = fork()) < 0)
    {
        perror("fork error:");
        exit(1);
    }
    //第一个子进程
    else if (pid == 0)
    {

        printf("I am the first child process.pid:%d\tppid:%d\n",getpid(),getppid());

      //在子进程内创建第二个子进程(即孙子进程)
        if ( (pid = fork()) < 0)
        {
            perror("fork error:");
            exit(1);
        }
        else if (pid >0)
        {
        //第一个子进程退出
            printf("first procee is exited.\n");
            exit(0);
        }
        //从这开始就是孙子进程的天下了
        //睡眠3s保证孙子进程的父进程(第一个子进程)退出,这样孙子进程就会被1号进程所收养,形成孤儿进程
        sleep(3);
        printf("I am the second child process.pid: %d\tppid:%d\n",getpid(),getppid());
        exit(0);
    }
    //父进程处理第一个子进程退出,注意子进程的pid在父进程中返回,所以在这直接等待pid的退出;
    if (waitpid(pid, NULL, 0) != pid)
    {
        perror("waitepid error:");
        exit(1);
    }
    exit(0);
    return 0;
}

二、避免僵尸进程之 ✈✈✈✈ wait、waitpid函数

  • wait函数
    函数原型:pid_t wait(int *status);
    1.功能:
    在父进程中执行,就是阻塞等待当前进程中任何一个子进程的退出,回收了子进程的资源;
    2.参数:
    status 用于保存退出子进程的退出状态,如果传递NULL,表示不关心子进程的退出状态;当子进程调用exit()函数退出子进程时,wait()函数status承接的就是exit()函数的退出值;
    3.返回值:
    成功返回:退出的子进程的pid号
    失败返回:返回 -1,同时errno被置为ECHILD

  • waitpid函数
    函数原型:pid_t waitpid(pid_t pid,int *status,int options);
    1.功能:等待指定的子进程结束(当子进程的状态发生改变时的处理函数)
    2.参数:
    第一个参数:pid
    常用:
    pid > 0,等待指定的pid子进程退出(表示只会等待指定子进程退出,对于其他的子进程的退出不做处理);
    pid = -1,等待任意一个子进程的退出;
    不常用:
    pid = 0,等待被调用进程同一组内任一子进程的退出;
    pid < -1,等待组ID等于pid绝对值的任一子进程的退出;
    第二个参数:status(同wait函数)
    第三个参数:options
    0,阻塞等待;
    返回值: 成功 返回pid号;失败 返回-1,此时errno会被设置;
    WNOHANG,非租塞等待;
    返回值: 成功 返回pid号(成功接收到);失败 返回0(没有接收到);

out_pid = wait(&ret);  <====等同====> out_pid = waitpid(pid, &ret, 0);

1.wait函数具体应用:

#include 
#include 
#include 
#include 
#include 
int main(int argc, const char *argv[])
{
    pid_t pid, out_pid;

    if( (pid = fork()) < 0)
    {
        perror("fork error");
        exit(1);
    }
    else if(pid == 0)
    {
        printf("im child process, and mypid = %d\n", getpid());
        sleep(3);
    }
    else if(pid > 0)
    {
        out_pid = wait(NULL);
        printf("im farther process, i wait the processpid = %d\n", out_pid);
    }

    return 0;
}

编译-执行(如下图)
进程学习:处理僵尸进程的两种方式_第1张图片

wait具体怎么玩儿的?
父进程调用了wait函数,将自己阻塞在wait函数。wait函数参数置为NULL,表示父进程不在意退出进程的状态值(即不在意exit()函数中传的是什么参数)。当子进程执行完毕或调用exit()后,wait会收集到这个子进程的信息,并将其销毁。wait返回退出的子进程的pid号。

为什么调用sleep(3);?
就是为了验证一下,wait函数会不会阻塞等待,因为父子进程在CPU调度时会抢占执行,看,现在父进程没有抢,而是在等子进程的退出。

刚才我们说了,wait承接的是exit()的参数(即子进程退出的状态值),现在我们来验证一下!
修改代码如下:

#include 
#include 
#include 
#include 
#include 
int main(int argc, const char *argv[])
{
    pid_t pid, out_pid;

    if( (pid = fork()) < 0)
    {
        perror("fork error");
        exit(1);
    }
    else if(pid == 0)
    {
        printf("im child process, and mypid = %d\n", getpid());
        sleep(3);
        exit(0);
        //exit(1);
        //exit(2);
    }
    else if(pid > 0)
    {
        int ret;
        out_pid = wait(&ret);
        printf("im farther process, i wait the processpid = %d\n", out_pid);
        printf("ret = %d\n", ret);
    }

    return 0;
}

我们分别执行exit(0)/exit(1)/exit(2),编译执行结果如下:
exit(0):
exit0
exit(1):
exit1
exit(2):
exit2
我们可以看到,系统将我们赋值给exit()的参数全部乘以了256.(具体原因老师讲的忘了)

结合着上边这个没解释清的东东,再来讲两个宏。(如果想看一下这两个宏的原型,可以vi -t WIFEXITED,如果vi -t用不了,参见我的其他博客)
如何判断子进程是否正常退出呢?
WIFEXITED():
1.功能:判断子进程是否为正常退出;
2.参数:注意不是&status,而是status,不是指针哦;
3.返回值:
如果子进程是正常退出,返回一个非零值;
如果子进程是非正常退出,返回0;
WEXITSTATUS():
1.功能:在子进程为正常退出的前提下,读取子进程的退出状态值;
2.参数:与WIFEXITED一样哦;
3.返回值:返回读到的子进程退出状态值;
来,一起验证验证一下:

#include 
#include 
#include 
#include 
#include 
int main(int argc, const char *argv[])
{
    pid_t pid, out_pid;

    if( (pid = fork()) < 0)
    {
        perror("fork error");
        exit(1);
    }
    else if(pid == 0)
    {
        printf("im child process, and mypid = %d\n", getpid());
        sleep(3);
        exit(2);
        //exit(1);
        //exit(0);
    }
    else if(pid > 0)
    {
        int ret;
        out_pid = wait(&ret);

        if(WIFEXITED(ret))
        {
            printf("WIFEXITED(ret) = %d, so child process exit normally\n");
            printf("child process exit num = %d\n", WEXITSTATUS(ret));
        }
        printf("im farther process, i wait the processpid = %d\n", out_pid);
        printf("ret = %d\n", ret);
    }

    return 0;
}

编译执行(如下图):
WIFEXITED


2.waitpid函数具体应用:

#include 
#include 
#include 
#include 
#include 
int main(int argc, const char *argv[])
{
    pid_t pid, out_pid;

    if( (pid = fork()) < 0)
    {
        perror("fork error");
        exit(1);
    }
    else if(pid == 0)
    {
        printf("im child process, and mypid = %d\n", getpid());
        sleep(3);
        exit(2);
        //exit(1);
        //exit(0);
    }
    else if(pid > 0)
    {
        int ret;
        //此时等同于wait(&ret);
        //out_pid = waitpid(pid, &ret, 0);

        //注意如果让waitpid是非阻塞模式下运行的话,要结合循环使用;
        //注意,这里的pid就是子进程的进程号,因为进程的特性就是子进程的进程号在父进程返回;
        while( (out_pid = waitpid(pid, &ret, WNOHANG)) == 0)
        {
            printf("no child process exited\n");
            sleep(1);
        }

        printf("child process %d is exit\n", out_pid);  
        //这里可以用下面这行来验证下子进程的pid在父进程中返回这句话;
        //printf("pid = %d\n", pid);
    }

    return 0;
}

编译执行(如下图)
进程学习:处理僵尸进程的两种方式_第2张图片
子进程睡眠了3秒,每1秒轮训一次,所以打印了3次”no child process exited”后,子进程退出,父进程获取到了子进程退出,释放其系统资源;

你可能感兴趣的:(fork,&,thread)