Linux:进程等待(进程控制三)

这篇博客我们将学习进程等待的相关知识,会比较难,不过没关系,我会带你一步一步梳理理解的

1.进程等待的必要性(为什么)

• 之前讲过,⼦进程退出,⽗进程如果不管不顾,就可能造成'僵⼫进程'的问题,进⽽造成内存
泄漏

• 另外,进程⼀旦变成僵⼫状态,那就⼑枪不⼊,"杀⼈不眨眼"的kill -9 也⽆能为⼒,因为谁也

没有办法杀死⼀个已经死去的进程

• 最后,⽗进程派给⼦进程的任务完成的如何,我们需要知道。如,⼦进程运⾏完成,结果对还是
不对,或者是否正常退出

• ⽗进程通过进程等待的⽅式,回收⼦进程资源(最重要),获取⼦进程退出信息(可选择,不算最重要

2.进程等待的⽅法

1.wait⽅法

我们先来写一个僵尸进程:

下面是code.c代码

cpp 复制代码
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>

int main()
{
    pid_t id = fork();
    //子进程
    if (id == 0)
    {
        for (int i = 0; i < 5; i++)
        {
            printf("我是一个子进程,pid : %d, ppid : %d\n", getpid(), getppid());
            sleep(1);
        }
        exit(0);
    }
    //父进程
    sleep(100);
    return 0;
}

运行结果如下图

我们通过man 2 wait来找到关于wait的信息

发现wait函数基本使用就是pid_t wait(int* status)

那么这里的rid是什么呢,如果等待成功就返回等待的子进程pid,不成功就返回-1

所以我们可以将上面code.c代码写为如下形式:

cpp 复制代码
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>

int main()
{
    pid_t id = fork();
    //子进程
    if (id == 0)
    {
        for (int i = 0; i < 5; i++)
        {
            printf("我是一个子进程,pid : %d, ppid : %d\n", getpid(), getppid());
            sleep(1);
        }
        exit(0);
    }
    //父进程
    sleep(10);
    pid_t rid = wait(NULL);
    if (rid > 0)
    {
        printf("等待成功,等待的子进程pid : %d\n", rid);
    }
    return 0;
}

运行结果:

等待成功,僵尸进程消失!!

至于status,我们在waitpid的时候讲,上面的代码实现了一个婴儿版的wait,如果需要知道更多信息,我们会需要使用waitpid了

2.waitpid⽅法

根据表得出,waitpid基本使用是pid_t waitpid(pid_t pid, int *status, int options);

这里面的options我们暂且不谈,到博客最后才谈论

这里面pid是什么呢?

我们通过man可以查看

我们不关心的:

pid < -1就是取绝对值,我们不用关心这个

pid = 0就是等待与当前进程同属一个进程组的所有子进程(即进程组 ID 相同的子进程)

我们关心的:

Pid = -1,等待任⼀个⼦进程,与wait等效

Pid > 0,等待其进程ID与pid相等的⼦进程

基于上面的描述(只需要传入id + 1),我们可以来看看如果等待失败,会发生什么

我们先将code.c代码改为如下

cpp 复制代码
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <errno.h>
#include <string.h>

int main()
{
    pid_t id = fork();
    //子进程
    if (id == 0)
    {
        for (int i = 0; i < 5; i++)
        {
            printf("我是一个子进程,pid : %d, ppid : %d\n", getpid(), getppid());
            sleep(1);
        }
        exit(0);
    }
    //父进程
    sleep(10);
    pid_t rid = waitpid(id + 1, NULL, 0);
    if (rid > 0)
    {
        printf("等待成功,等待的子进程pid : %d\n", rid);
    }
    else 
    {
        printf("等待失败,%d - > %s\n", errno, strerror(errno));
    }
    return 0;
}

运行结果

发现打印出了错误码和对应的错误信息

这里面status是什么呢?

其实就是我们学习进程退出时的退出码,我们通过指针方式得到退出码

我们可以实验一下,下面的代码子进程结束以后会返回1,即退出码为1

cpp 复制代码
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <errno.h>
#include <string.h>

int main()
{
    pid_t id = fork();
    //子进程
    if (id == 0)
    {
        for (int i = 0; i < 3; i++)
        {
            printf("我是一个子进程,pid : %d, ppid : %d\n", getpid(), getppid());
            sleep(1);
        }
        exit(1);
    }
    //父进程
    sleep(6);
    int status = 0;
    pid_t rid = waitpid(id, &status, 0);
    if (rid > 0)
    {
        printf("等待成功,等待的子进程pid : %d, status : %d\n", rid, status);
    }
    else 
    {
        printf("等待失败,%d - > %s\n", errno, strerror(errno));
    }
    return 0;
}

运行结果

很奇怪,为什么是256呢,不应该是1吗???

下面为你解答

• wait和waitpid,都有⼀个status参数,该参数是⼀个输出型参数,由操作系统填充。

• 如果传递NULL,表⽰不关⼼⼦进程的退出状态信息。

• 否则,操作系统会根据该参数,将⼦进程的退出信息反馈给⽗进程。

• status不能简单的当作整形来看待,可以当作位图来看待,具体细节如下图(只研究status低16

⽐特位)

其中前8个存放我们的的退出码,后8个存放我们的退出信息(是否异常退出(也就是kill 的对应数字)),所以我们刚刚exit(1),正常退出,即0000 0000 0000 0000 0000 0001 0000 0000

所以是256

所以要想获取真正的退出码,我们只需要位运算一下:(status >> 8) & 0xFF

同理获取退出状态:status & 0x7F

下面是kill的全部信息

我们为了获得这些东西,我们修改一下code.c并且运行

cpp 复制代码
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <errno.h>
#include <string.h>

int main()
{
    pid_t id = fork();
    //子进程
    if (id == 0)
    {
        for (int i = 0; i < 3; i++)
        {
            printf("我是一个子进程,pid : %d, ppid : %d\n", getpid(), getppid());
            sleep(1);
        }
        exit(1);
    }
    //父进程
    sleep(6);
    int status = 0;
    pid_t rid = waitpid(id, &status, 0);
    if (rid > 0)
    {
            printf("等待成功,等待的子进程pid : %d, exit code : %d, exit signal : %d\n", rid, (status >> 8) & 0xFF, status & 0x7F);
    }
    else 
    {
        printf("等待失败,%d - > %s\n", errno, strerror(errno));
    }
    return 0;
}

得到退出码为1,退出状态为0,即正常退出

如果我们想得到非正常退出的怎么办呢,我们只需要改变一下code.c就行

cpp 复制代码
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <errno.h>
#include <string.h>

int main()
{
    pid_t id = fork();
    //子进程
    if (id == 0)
    {
        while (1)
        {
            printf("我是一个子进程,pid : %d, ppid : %d\n", getpid(), getppid());
            sleep(1);
        }
        exit(1);
    }
    //父进程
    sleep(6);
    int status = 0;
    pid_t rid = waitpid(id, &status, 0);
    if (rid > 0)
    {
            printf("等待成功,等待的子进程pid : %d, exit code : %d, exit signal : %d\n", rid, (status >> 8) & 0xFF, status & 0x7F);
    }
    else 
    {
        printf("等待失败,%d - > %s\n", errno, strerror(errno));
    }
    return 0;
}

运行发现exit signal为9,对应kill -9 pid命令,此时退出码为0,无意义(他都异常了,不需要退出码了)

3.status怎么获取,存放哪里

怎么获取

我们上面一直在谈论status的用法,但是我们需要思考一下他从哪里来的,我们不可以直接从系统拿到status,所以只能通过系统调用(也就是传入一个status指针)来获得

存放哪里

僵尸进程会保留pcb,等待父进程回收,所以我们可以大胆推测,是不是pcb里面有对应的东西存放exid_code与exid_signal??对的!!

所以我们的exid_code与exid_signal就存放在pcb里面,等待父进程拿到子进程的exid_code与exid_signal

对应下图

linux的pcb源代码也是这样的

!!确实是这样的,所以为什么要有僵尸状态,我们也就理解了

4.关于status的一些扩展操作

操作系统不希望我们直接使用位操作来通过status来获取exid_code与exid_signal,所以提供了宏定义,我们主要了解下面两个即可

WEXITSTATUS(status): 若WIFEXITED⾮零,提取⼦进程退出码。(查看进程

的退出码)

WIFEXITED(status): 若为正常终⽌⼦进程返回的状态,则为真。(查看进程

是否是正常退出

下面我们使用这两个宏做一下实验,code.c代码如下

cpp 复制代码
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <errno.h>
#include <string.h>

int main()
{
    pid_t id = fork();
    //子进程
    if (id == 0)
    {
        while (1)
        {
            printf("我是一个子进程,pid : %d, ppid : %d\n", getpid(), getppid());
            sleep(1);
        }
        exit(1);
    }
    //父进程
    sleep(6);
    int status = 0;
    pid_t rid = waitpid(id, &status, 0);
    if (rid > 0)
    {
        //printf("等待成功,等待的子进程pid : %d, exit code : %d, exit signal : %d\n", rid, (status >> 8) & 0xFF, status & 0x7F);
        if (WIFEXITED(status))
            printf("等待成功,等待的子进程pid : %d, exit code : %d\n", rid, WEXITSTATUS(status));
        else 
            printf("子进程异常退出\n"); //拿不到exit signal,需要拿的时候使用最开的方法
    }
    else 
    {
        printf("等待失败,%d - > %s\n", errno, strerror(errno));
    }
    return 0;
}

5.options的使用

先了解阻塞与非阻塞:

对于阻塞我们已经学过了,如果我们让父进程一直等待子进程退出,那么就相当于让父进程一直阻塞,父进程干不了别的事情,只能等待子进程退出

于是我们有了非阻塞(非阻塞轮询),就可以让父进程每隔一段时间来看一下子进程是否结束,就可以节约等待的时间来干别的事情(比如运行别的进程)

简单来说:阻塞是 "专注等待,啥也不干",非阻塞是 "等待的同时,能抽空干别的"

之前我们使用阻塞调用,只有rid < 0 和 > 0的情况,至于=0,是非阻塞专有的,指的是父进程调用结束,但是子进程没有结束,下面我们用实验来验证

下面是code.c代码

cpp 复制代码
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <errno.h>
#include <string.h>

int main()
{
    pid_t id = fork();
    //子进程
    if (id == 0)
    {
        int cur = 10;
        while (cur--)
        {
            printf("我是一个子进程,pid : %d, ppid : %d\n", getpid(), getppid());
            sleep(1);
        }
        exit(1);
    }
    //父进程
    //sleep(6);
    int status = 0;
    while (1)
    {
        pid_t rid = waitpid(id, &status, WNOHANG);
        if (rid == 0)
        {
            printf("子进程未结束!\n");
            sleep(2);
        }
        else if (rid > 0)
        {
            printf("子进程结束!\n");
            break;
        }
        else 
        {
         printf("等待失败");
            break;
        }
    }

    //pid_t rid = waitpid(id, &status, 0);
    //if (rid > 0)
    //{
    //    //printf("等待成功,等待的子进程pid : %d, exit code : %d, exit signal : %d\n", rid, (status >> 8) & 0xFF, status & 0x7F);
    //    if (WIFEXITED(status))
    //        printf("等待成功,等待的子进程pid : %d, exit code : %d\n", rid, WEXITSTATUS(status));
    //    else 
    //        printf("子进程异常退出\n"); //拿不到exit signal,需要拿的时候使用最开的方法
    //}
    //else 
    //{
    //    printf("等待失败,%d - > %s\n", errno, strerror(errno));
    //}
    return 0;
}

每两秒轮询一次

好啦,这就是进程等待的内容啦,感觉有帮助的可以收藏点赞哦~~

相关推荐
The star"'1 小时前
docker swarm和containerd
运维·docker·容器
云飞云共享云桌面1 小时前
研发部门使用SolidWorks,三维设计云桌面应该怎么选?
运维·服务器·前端·网络·自动化·电脑
干啥都是小小白1 小时前
Linux 驱动
linux·运维·服务器
向山行_wolf1 小时前
ubuntu20.04安装向日葵
linux·运维·服务器
❀͜͡傀儡师1 小时前
Docker部署OneTerm堡垒机
运维·docker·容器·oneterm
菜鸟小九1 小时前
mysql运维(主从复制)
android·运维·mysql
飘忽不定的bug1 小时前
记录:ubuntu20.04隐藏鼠标指针
linux·ubuntu
松涛和鸣1 小时前
23、链式栈(LinkStack)的实现与多场景应用
linux·c语言·c++·嵌入式硬件·ubuntu
虾..1 小时前
Linux 进程替换
linux·运维·服务器