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;
}

每两秒轮询一次

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

相关推荐
AOwhisky15 小时前
Python 学习笔记(第十五期)——运维自动化(下·后篇):堡垒机实战——paramiko高阶篇
运维·python·学习·云原生·自动化·运维开发
重生的黑客15 小时前
Linux 进程优先级、切换与调度:从孤儿进程到 O(1) 调度模型
linux·运维·服务器·进程优先级·nice
Urbano15 小时前
夹克与商务单西口袋工序自动化技改研究报告
运维·自动化
骑上单车去旅行16 小时前
MD5校验对比脚本
linux·服务器·windows
平生幻17 小时前
Linux 常用命令
linux
期待着201317 小时前
docker 安装 ,在centos7.9
运维·docker·容器
踏月的造梦星球17 小时前
DM8 DSC 单机双实例部署
运维·开发语言·数据库
MXsoft61817 小时前
平台化是智能运维发展的必然趋势吗?
运维
ShirleyWang01218 小时前
让headlamp控制台能访问
linux·服务器·python·k8s·k3s
liuccn18 小时前
Linux 存储系统:LVM 与直接分区
linux·运维