【Linux】:进程等待

朋友们、伙计们,我们又见面了,本期来给大家解读一下有关Linux进程等待的相关知识点,如果看完之后对你有一定的启发,那么请留下你的三连,祝大家心想事成!

C 语 言 专 栏:C语言:从入门到精通

数据结构专栏:数据结构

个  人  主  页 :stackY、

C + + 专 栏   :C++

Linux 专 栏  :Linux

​ 

1. 进程等待

通过系统调用接口(wait/waitpid)的方式,让父进程对子进程进行资源回收的等待过程。

2. 进程等待的必要性

1. 在之前的僵尸进程这一章节提到过,子进程在退出之后,父进程不进行回收资源,会造成内存泄露的问题,简而言之就是要解决僵尸进程带来的内存泄露问题。

2. 父进程创建子进程的目的就是为了让子进程做一些事情,那么父进程是需要知道子进程把事情完成的怎么样了,就需要通过等待来获取进程退出的信息(两个数字),获取这两个数字并不是必须的,但是系统需要提供这样的基础功能!

3. 进程等待的方法 

可以使用系统调用接口:wait()或者waitpid()

3.1 wait

wait可以等待任意一个进程。 

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

pid_t wait(int*status);

//返回值:
//成功返回被等待进程pid,失败返回-1。

//参数:
//输出型参数,获取子进程退出状态,不关心则可以设置成为NULL

我们先不关心它的参数,直接设置为NULL,先来看一下能否回收掉子进程的僵尸问题:

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

void Worker()
{
    int cnt = 5;
    while (cnt--)
    {
        sleep(1);
        printf("I am child process, pid: %d, ppid: %d, cnt: %d\n", getpid(), getppid(), cnt);
    }
}

int main()
{
    pid_t id = fork();
    if (id == 0)
    {
        // child
        Worker();
        exit(10); // 退出码设置为10
    }
    else
    {
        // parent
        sleep(10);
        pid_t rid = wait(NULL);
        if (rid == id) // 等待成功
        {
            printf("wait success, pid: %d, rpid: %d, \n", getpid(), rid);
        }
        sleep(5);
    }
    return 0;
}

上面这段代码我们期望看到的效果是:刚开始有两个进程在运行,5s之后子进程变成了僵尸状态,再过5s,父进程等待回收子进程,就剩下一个进程在运行。

如果子进程不退出我们就开始等待会发生什么呢?

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

void Worker()
{
    int cnt = 5;
    while (cnt--)
    {
        sleep(1);
        printf("I am child process, pid: %d, ppid: %d, cnt: %d\n", getpid(), getppid(), cnt);
    }
}

int main()
{
    pid_t id = fork();
    if (id == 0)
    {
        // child
        Worker();
        exit(10); // 退出码设置为10
    }
    else
    {
        // parent
        //sleep(10);
        printf("wait befor\n");
        pid_t rid = wait(NULL);
        printf("wait after\n");

        if (rid == id) // 等待成功
        {
            printf("wait success, pid: %d, rpid: %d, \n", getpid(), rid);
        }
        sleep(5);
    }
    return 0;
}

通过实验可以看到,如果子进程没有退出,那么父进程就必须在wait上进行阻塞等待,直到子进程结束,wait自动回收并返回!

一般而言,父子进程谁先运行并不确定,但可以确定的是,父进程一定是最后退出的。

3.2 waitpid 

waitpid可以等待指定的进程。

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

pid_ t waitpid(pid_t pid, int *status, int options);

//返回值:
//当正常返回的时候waitpid返回收集到的子进程的进程ID;
//如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0;
//如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在;

参数:

pid:等待指定的进程id,设置为-1表示等待任意进程。

*status:获取子进程的状态,不关注可以设置为NULL。

optons:等待的方法,设置为0默认为阻塞式等待。

3.2.1 *status 

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

通过等待的方式解决了僵尸进程导致的内存泄漏问题,那么如何获取子进程的退出结果呢?

*status是输出型参数,我们可以定义一个status,然后传递给wait/waitpid,然后将返回信息带给我们。

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

void Worker()
{
    int cnt = 5;
    while (cnt--)
    {
        sleep(1);
        printf("I am child process, pid: %d, ppid: %d, cnt: %d\n", getpid(), getppid(), cnt);
    }
}

int main()
{
    pid_t id = fork();
    if (id == 0)
    {
        // child
        Worker();
        exit(10); // 退出码设置为10
    }
    else
    {
        // parent
        sleep(10);
        // pid_t rid = wait(NULL);
        int status = 0;  // 定义退出信息
        pid_t rid = waitpid(id, &status, 0);
        if (rid == id) // 等待成功
        {
            printf("wait success, pid: %d, rpid: %d, exit code: %d\n", getpid(), rid, status);
        }
        sleep(5);
    }
    return 0;
}

可以看到明明我们设置的退出码是10,为什么打印出了2560呢?

status是一个整形指针变量,有32个比特位,我们不能简单的当做整形来看待,要当做位图来看待,我们研究status只观察低16位:

 

正常结束:次低8位(8 ~ 15)表示的是进程退出码;

异常终止:低8位(0 ~ 7)表示的是异常终止信号。

正常结束时,退出码为10对应的status就是0000 1010 0000 0000转化为10进制就是2560

所以我们要正确的得到进程的退出码需要给status右移8位,按位于0xFF((status >> 8) & 0xFF);

正确得到进程终止信号需要给status按位于0x7F(status & 0x7F)。

int main()
{
    pid_t id = fork();
    if (id == 0)
    {
        // child
        Worker();
        exit(10); // 退出码设置为10
    }
    else
    {
        // parent
        sleep(10);
        // pid_t rid = wait(NULL);
        int status = 0; // 定义退出信息
        pid_t rid = waitpid(id, &status, 0);
        if (rid == id) // 等待成功
        {
            printf("wait success, pid: %d, rpid: %d, exit sig: %d, exit code: %d\n", getpid(), rid, status & 0x7F, (status >> 8) & 0xFF);
        }
        sleep(5);
    }
    return 0;
}

关于status参数对退出码的提取还有一种简便的方式:现在外面定义status。

WIFEXITED(status)   // 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)
WEXITSTATUS(status) // 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)
if (WIFEXITED(status))  // 正常退出
{
    printf("child process normal quit, exit code : %d\n", WEXITSTATUS(status));
} 
else                    // 异常终止 
{
    printf("child process quit except!\n");
}

① 当一个进程异常终止,此时的退出码就毫无意义。

② 如何判断有没有收到信号呢?只要终止信号为0(终止信号范围在1 ~ 64),就表示程序运行正常。

为什么不用全局变量来获取子进程的退出信息呢?而要用系统调用?

当父子进程要对全局变量写入时,会发生写时拷贝,又因为进程具有独立性,所以父进程是无法直接获得子进程的退出信息的。 

3.2.2 options

该参数表示等待的方式:

0表示阻塞式等待;

WNOHANG表示非阻塞式等待。

通过一个例子来理解一下阻塞与非阻塞等待:

如果我们要下载一个游戏,那么在下载的这段时间我们就啥也不干,直勾勾的盯着它,直到它下载好,这种行为就叫做阻塞式等待;

当游戏下载的这段时间,我们可以先看看它下没下好,没下好我们就可以看看视频,听听音乐,然后再看看它下没下好,没下好我们就再干干别的事情,直到它下载好,这种行为就叫做非阻塞式等待。

对比等待进程:

  • 阻塞式调用:使用wait/waitpid等待子进程时,如果子进程不退出,wait/waitpid不返回。
  • 非阻塞式调用:使用waitpid等待子进程时,如果我们等待的条件不满足,不阻塞,而是直接返回!

非阻塞等待往往需要重复调用,以非阻塞轮询的方案进行进程等待,它的好处就在于,当我们进行进程等待的过程中,可以顺便做一下自己占据时间并不多的事情。

当options设置为WNOHANG时,waitpid的返回值有三种情况:

  • rid > 0:等待成功
  • rid == 0:等待成功,但是对方还有退出
  • rid < 0:等待失败(pid填写错误)

下面我们就来用代码的方式来演示一下非阻塞等待:

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

void Worker()
{
    int cnt = 5;
    while (cnt--)
    {
        printf("I am child process, pid: %d, ppid: %d, cnt: %d\n", getpid(), getppid(), cnt);
        sleep(2);
    }
}

int main()
{
    pid_t id = fork();
    if (id == 0)
    {
        // child
        Worker();
        exit(0); // 退出码设置为10
    }
    else
    {
        while (1) // 非阻塞轮询
        {
            int status = 0; // 定义退出信息
            pid_t rid = waitpid(id, &status, WNOHANG);
            if (rid > 0) // 等待成功
            {
                printf("child quit success, pid: %d, exit sig: %d, exit code: %d\n", getpid(), status & 0x7F, (status >> 8) & 0xFF);
                break;
            }
            else if (rid == 0) // 等待成功,但并未退出
            {
                printf("child is alive, wait again, father do other thing....\n");
                // 可以设置一些任务...
            }
            else // 等待失败
            {
                printf("wait failed!\n");
                break;
            }
            sleep(1);
        }
    }
    return 0;
}

3.3 OS层面的进程等待

 根据冯诺依曼体系结构,wait和waitpid属于系统调用,而进程在OS的内存中运行,我们再进行进程等待的时候,使用系统调用接口,要拿到子进程的退出信息,所以我们在外部定义了一个变量status,当子进程执行完毕之后,OS会执行它的退出程序,将进程的Z状态修改为X状态,并将子进程的的退出码和退出信号写入到它的PCB中,这时就可以通过父进程中的status指针来获取子进程的退出信息。

创建多进程循环fork,等待多进程循环waitpid,waitpid第一个参数设置为-1,表示等待任意一个进程。 

朋友们、伙计们,美好的时光总是短暂的,我们本期的的分享就到此结束,欲知后事如何,请听下回分解~,最后看完别忘了留下你们弥足珍贵的三连喔,感谢大家的支持!      

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/785967.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

电竞玩家的云端盛宴!四大云电脑平台:ToDesk、顺网云、青椒云、极云普惠云实测大比拼

本文目录 一、云电脑概念及市场需求二、云电竞性能测试2.1 ToDesk云电脑2.2 顺网云2.3 青椒云2.4 极云普惠云电脑 三、四大云电脑平台综合配置对比3.1 CPU处理器3.2 GPU显卡3.3 内存 四、总结 一、云电脑概念及市场需求 在数字化时代的推动下&#xff0c;云计算技术日益成熟&a…

JAVA 代码块介绍

一、基本介绍 代码化块又称为初始化块&#xff0c;属于类中的成员[即 是类的一部分]&#xff0c;类似于方法&#xff0c;将逻辑语句封装在方法体中&#xff0c;通过包围起来。 但和方法不同&#xff0c;没有方法名&#xff0c;没有返回&#xff0c;没有参数&#xff0c;只有方…

Java面试八股之MySQL支持哪些数据类型

MySQL支持哪些数据类型 MySQL支持多种数据类型&#xff0c;这些类型可以大致分为三大类&#xff1a;数值类型、日期/时间类型和字符串类型。下面是一些常见的数据类型及其用途&#xff1a; 数值类型 整数类型&#xff1a; TINYINT&#xff1a;通常占用1字节。 SMALLINT&am…

注册商标为什么要先查询

注册商标为什么要先查询 在知识产权日益受到重视的今天&#xff0c;商标的注册成为了许多企业和个人保护其品牌价值和市场地位的重要手段。然而&#xff0c;商标注册并非一蹴而就的过程&#xff0c;其中一个关键的步骤就是商标查询&#xff0c;也就是我们通常所说的“商标检索…

STM32CubeMX如何配置生成项目以及安装包

目录 一、STM32CubeMX介绍 二、用STM32CubeMX生成项目 1.创建项目 2.定义引脚 3.配置时钟 4.保存项目 5.生成项目 6.打开项目 一、STM32CubeMX介绍 STM32CubeMX是STM32Cube工具家族中的一员&#xff0c;专门为STM32微控制器的开发提供便利。它是一款图形化工具&#xf…

新加坡工作和生活指北:租房篇

本文首发于公众号 Keegan小钢 前段时间已经分享了工作篇&#xff0c;现在接着聊聊生活篇。因为生活这块涉及到多个方面&#xff0c;内容比较多&#xff0c;所以我再细分了一下&#xff0c;本篇先聊聊租房。 先来看看新加坡的地区分布图&#xff0c;如下&#xff1a; 上图将新加…

【C语言】指针(3):探索-不同类型指针变量

目录 一、字符指针变量 二、数组指针变量 三、二维数组传参的本质 四、函数指针变量 4.1 函数指针变量 4.2 函数指针变量的使用 4.3 函数指针变量的拓展 五、函数指针数组 六、转移表的应用 通过深入理解指针&#xff08;1&#xff09;和深入理解指针&#xff08;2&am…

【北京迅为】《i.MX8MM嵌入式Linux开发指南》-第一篇 嵌入式Linux入门篇-第十二章 Linux 权限管理

i.MX8MM处理器采用了先进的14LPCFinFET工艺&#xff0c;提供更快的速度和更高的电源效率;四核Cortex-A53&#xff0c;单核Cortex-M4&#xff0c;多达五个内核 &#xff0c;主频高达1.8GHz&#xff0c;2G DDR4内存、8G EMMC存储。千兆工业级以太网、MIPI-DSI、USB HOST、WIFI/BT…

Python 插入、替换、提取、或删除Excel中的图片

Excel是主要用于处理表格和数据的工具&#xff0c;我们也能在其中插入、编辑或管理图片&#xff0c;为工作表增添视觉效果&#xff0c;提升报告的吸引力。本文将详细介绍如何使用Python操作Excel中的图片&#xff0c;包含以下4个基础示例&#xff1a; 文章目录 Python 在Excel…

Autogen基本使用介绍

文章目录 一&#xff0c;Build1&#xff0c;Skill2&#xff0c;Models3&#xff0c;agents4,workflow 二&#xff0c;Playground 本文唯一目的就是介绍一下Autogen Studio的基本的使用。 打开这个网页以后&#xff0c;看到它有2个菜单&#xff0c;分别是&#xff1a; BuildPla…

07-7.3.2 平衡二叉树(AVL)

&#x1f44b; Hi, I’m Beast Cheng &#x1f440; I’m interested in photography, hiking, landscape… &#x1f331; I’m currently learning python, javascript, kotlin… &#x1f4eb; How to reach me --> 458290771qq.com 喜欢《数据结构》部分笔记的小伙伴可以…

使用 Qt 和 ECharts 进行数据可视化

文章目录 示例图表预览折线图散点图柱状图使用 Qt 和 ECharts 进行数据可视化一、准备工作1. 安装 Qt2. 准备 ECharts二、在 Qt 中使用 ECharts1. 创建 Qt 项目2. 配置项目文件3. 在 UI 中添加 WebEngineView4. 加载 ECharts三、创建折线图、散点图和柱状图1. 折线图2. 散点图3…

工作流之战: Flowable vs. Camunda vs. Activiti

欢迎来到我的博客&#xff0c;代码的世界里&#xff0c;每一行都是一个故事 &#x1f38f;&#xff1a;你只管努力&#xff0c;剩下的交给时间 &#x1f3e0; &#xff1a;小破站 工作流之战: Flowable vs. Camunda vs. Activiti 前言功能特性对比架构设计分析性能比较使用场景…

zookeeper加入开机启动项

Windows的任务计划程序&#xff08;Task Scheduler&#xff09;是一个强大的工具&#xff0c;允许你安排程序在特定时间自动运行&#xff0c;包括开机时。 打开任务计划程序&#xff1a; 按下Win R键&#xff0c;打开“运行”对话框。输入taskschd.msc并回车&#xff0c;打开…

js ES6 part1

听了介绍感觉就是把js在oop的使用 作用域 作用域&#xff08;scope&#xff09;规定了变量能够被访问的“范围”&#xff0c;离开了这个“范围”变量便不能被访问&#xff0c; 作用域分为&#xff1a; 局部作用域、 全局作用域 1. 函数作用域&#xff1a; 在函数内部声明的…

Docker定时清理

一、循环调度执行 1、检查cron状态 systemctl status crond 2、创建要执行的shell脚本 vim /home/cleanup_docker.sh #! /bin/bash # 清理临时文件 echo $(date "%H:%M:%S") "执行docker清理命令..." docker system prune -af-a 清理包括未使用的镜像 …

Vue3动态路由(响应式带参数的路由)变更页面不刷新的问题

背景 先说说问题&#xff0c;问题来源是因为我的开源项目Maple-Boot项目的网站前端&#xff0c;因为项目主打的内容发布展示&#xff0c;所以其中的内容列表页会根据不同的菜单进行渲染不同的路由。 这里路由path使用的是/blog/:menu?&#xff0c;通过menu的参数来渲染对应的…

很多人对AI Agent的理解太片面

现在 AI 智能体&#xff08;AI Agent&#xff09;的概念很火&#xff0c;似乎 Agent 是用 AI 解决问题的银弹&#xff0c;有了 Agent 就可以解决很多问题。但也有很多人有不同意见&#xff0c;认为 Agent 不过是噱头&#xff0c;并没有看到靠谱的应用场景。 一个被提及很多的是…

openlayers更改点坐标

我现在的需求是无人机点位根据ws传输的经纬度改变位置&#xff0c;在网上查了很多资料&#xff0c;终于是做出来了&#xff0c;如果有问题请指出。 效果图&#xff0c;无人机可以来回移动 这里是核心代码 // 添加飞机点位图层let vectorLayerpointfunction DronepointLayer()…

Windows远程桌面的奇技淫巧

前言 Windows远程桌面简介 远程桌面协议(RDP)是一个多通道(multi-channel)的协议&#xff0c;让使用者连上提供微软终端机服务的计算机(称为服务端或远程计算机) 远程桌面的前置条件 在获取权限后&#xff0c;针对3389进行展开&#xff0c;先查询3389端口是否开启 netstat…