Linux——进程控制(二)进程等待

目录

前言

一、进程等待

二、如何进行进程等待

1.wait

2.waitpid

2.1第二个参数 

2.2第三个参数 

3. 等待多个进程

三、为什么不用全局变量获取子进程的退出信息


前言

前面我们花了大量的时间去学习进程的退出,退出并不难,但更深入的学习能为本章进程等待打好基础,因此没看过的小伙伴可以先学习进程退出。

一、进程等待

之前讲过,子进程退出,父进程一直在运行,不对子进程进行回收,就可能造成‘僵尸进程’的问题,进而造成内存泄漏

另外,进程一旦变成僵尸状态,那就刀枪不入,“杀人不眨眼”的kill -9 也无能为力,因为谁也没有办法 杀死一个已经死去的进程。

父进程派给子进程的任务完成的如何,我们需要知道。如子进程运行完成,结果对还是不对, 或者是否正常退出。

父进程通过进程等待的方式,可以获取子进程退出的信息。(虽然不是一定要获取,但是得有这个功能)

二、如何进行进程等待

1.wait

我们看看2号手册中的wait函数,他可以等待任意一个子进程的退出,参数是int类型的指针,等待成功返回子进程的pid,失败返回-1。

我们使用如下代码进行进程等待,这里wait的参数先给NULL,代表不关心子进程退出的状态(后续会再提到)。子进程运行5秒后变成僵尸状态,父进程先休眠10秒再去调用wait函数。

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

void Work()
{
    int cnt = 5;
    while(cnt)
    {
        printf("我是子进程, pid: %d, ppid: %d, cnt: %d\n",getpid(),getppid(),cnt--);
        sleep(1);
    }
}

int main()
{
    pid_t id = fork();
    if(id == 0)
    {
        //child
        Work();
        exit(0);
    }
    else
    {
        sleep(10);
        //father
        pid_t rid = wait(NULL);
        if(rid == id)
        {
            printf("等待成功,pid: %d\n",getpid());
        }
    }
                                                                                    
    return 0;
}

我们写了一个脚本来监控进程的运行情况,代码如下(注意myprocess是我设置的进程名)

 while :; do ps ajx | head -1 && ps ajx | grep myprocess | grep -v "grep"; sleep 1; echo "###################"; done

结果发现0-5秒中,父子进程正常运行,5-10秒中,子进程变成了僵尸状态,父进程此时在sleep,并没有回收子进程,10秒后,父进程sleep结束,wait函数等到了子进程,于是将子进程回收了,同时父进程也运行完毕。 

由此我们可以得知:

进程等待能回收子进程僵尸状态。

还有一个结论,在父进程进行等待的时候,如果子进程还没有处理完,那么父进程必须在wait上进行阻塞等待,直到子进程僵尸,wait自动回收,再继续执行后续代码。这可以通过打印的方式查看。就类似于scanf需要等待用户输入一样,用户不输入就一直在这里阻塞着,直到输入后才继续往后执行。

一般而言,父子进程谁先运行我们不知道,但能知道一般都是父进程最后退出,多进程由父进程发起,也由父进程统一回收

2.waitpid

wait是等待任意一个子进程,而waitpid可以等待指定的那一个,第一个参数传等待子进程的pid代表等待这个进程,传-1代表等待任意进程。 第二个参数和wait的参数一样,第三个参数也先不管,设置为0代表默认阻塞等待。

将上面的代码从wait修改为waitpid,因为我们只fork了一次,只创建了一个子进程,因此如下修改即可。

2.1第二个参数 

重点我们得讲解一下第二个参数 status ,他是输出型参数,我们可以随便定义一个int变量,将变量的值传递给第二个参数,waitpid会将子进程退出码和信号写到这个变量里

这里我们将子进程的退出码设置为10,看看打印出来的status值为多少。

发现status为2560,这似乎不像退出码。他是通过下面这个图片的方式得来的,int整形32位,只用低16位,其中高8位代表退出码,低8位代表终止信号

正常终止看高八位即可,因为未收到信号,因此低8位为0。

被信号所杀,看低八位,其中第7位不看,他代表core dump标志(暂时不考虑),只看0-6位。 

那么2560的二进制为 0000 1010 0000 0000 如果右移8位,也就是只看高8位,即0000 1010,即为10,我们退出码也就是10。

公式为  :  *status = (exit_code<<8)| exit_signal

status不能直接使用,如下经过右移操作和与操作,就可以得到准确的退出码和信号了。

执行一下,没有问题 

小总结一下

  • 当一个进程异常了(收到信号) ,那么退出码就无意义了
  • 通过信号码是否非零,来判断是否收到信号
  • 手动杀死子进程,也能得到相应信号

虽然我们会通过位运算来得到退出码与信号,但这样也比较麻烦,linux系统提供了如下两个接口帮我们处理status。

WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)

WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)

如下,查看是否正常退出,退出码为多少。 

结果也符合预期 

2.2第三个参数 

0:即阻塞等待

WNOHANG::若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则返回该子进程的ID。(非阻塞式等待)

讲个小故事:

        张三约翠花去电竞酒店打麻将, 他已经到翠花楼下了,现在在等待翠花先来一起出发。

        此时张三有两个方法,一个是打电话,询问翠花在干嘛,什么时候下楼,打完翠花说等一下,她化个妆,于是张三就在楼下开一把金铲铲之战,过了半个小时又打,翠花说在穿鞋了等一小下,于是张三挂断电话去刷抖音,过一会再打电话,翠花说已经下楼了,刚刚准备出门又上了个厕所,张三没有什么脾气,谁叫我想约人家呢,于是挂断电话,又去看看淘宝,要买点什么,最后再打电话,翠花此时终于到达了,于是两个人开开心心的去打麻将了。

        另一个方法也是打电话,询问翠花在干嘛,什么时候下楼,翠花也说等一下,还在化妆,但是张三今天电话不挂,就一直等翠花,时刻知道翠花在干嘛,直到翠花下楼一起去打麻将。

在这个故事中

张三:父进程

翠花:子进程

打电话:调用系统接口

第一个方法:等待的条件不满足,wait/waitpid不阻塞,而是立即返回!可以做自己占据时间并不多的事情。这是非阻塞式调用,即非阻塞+轮询方案进行进程等待,该方案往往要进行重复调用。返回值>0等待成功,子进程已退出;返回值==0;等待成功,子进程未退出,返回值<0等待失败

第二个方法:翠花不结束,电话不挂机,即阻塞式调用。子进程不退出,wait/waitpid不返回。

代码如下,waitpid第三个参数为 WNOHANG 借此观看非阻塞轮询等待。 

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

void Work(int number)
{
    printf("我是子进程, pid: %d, ppid: %d, number: %d\n",getpid(),getppid(),number);
}

int main()
{
    pid_t id = fork();
    if(id == 0)
    {
        //child
        int number = 5;
        while(number)
        {
            Work(number);
            number--;
            sleep(1);
        }
        exit(10);
    }
    //father                                                                                          
    int status = 0;
    while(1)
    {
        pid_t rid = waitpid(id,&status,WNOHANG);
        if(rid>0)
        {
            //等待成功,子进程退出了
            printf("等待子进程成功,子进程退出码: %d,退出信号: %d\n",WEXITSTATUS(status),status&0x7F);
            break;
        }
        else if(rid == 0)
        {
            //等待成功,但子进程没有退出
            printf("等待成功,子进程还没推出,父进程做其他事情去了\n");
            sleep(2);
        }
        else
        {
            printf("等待失败\n");
            break;
        }
    }
    return 0;
}

运行结果如下,父进程间歇性询问子进程是否完成,没完成就做自己的事情,待会再来询问。

3. 等待多个进程

我们使用for循坏来fork多个进程,同时给每个进程编号,创建顺序从0-9。waitpid第一个参数为-1,代表等待任意的子进程。

虽然我们也可以用数组的方式,将子进程的pid放到数组里,但是这样就只能一个一个进程的等待,比如最先会等待退出码为0进程,如果该进程不结束,父进程会一直等待,那么后续的进程永远不会被回收,就会造成内存泄漏的问题。

 1: myprocess.c ? ?                                                                        ?? buffers 
#include<sys/types.h>
#include<sys/wait.h>

void Work(int number)
{
    int cnt = 2;
    while(cnt)
    {
        printf("我是子进程, pid: %d, ppid: %d, cnt: %d, number: %d\n",getpid(),getppid(),cnt--,number);
        sleep(1);
    }
}

const int n = 10;

int main()
{
    int i = 0;
    for(;i<n;i++)
    {
        pid_t id = fork();
        if(id == 0)
        {
            //child
            Work(i);
            exit(i);
        }
    }
    //fork的子进程已近全部退出了,下面是父进程执行的代码
    for(i=0;i<n;i++)
    {
        int status;
        pid_t rid = waitpid(-1,&status,0);  //-1:任意一个子进程退出 
        if(rid>0)                                                                                      
        {
            printf("等待子进程 %d 成功, 退出码: %d\n",rid, WEXITSTATUS(status));
        }
    }
    return 0;
}

看看运行的情况,发现调度运行与终止都是没有规律的,谁先谁后我们不确定,我们只知道肯定是父进程先创建并最后退出。

三、为什么不用全局变量获取子进程的退出信息

刚刚我们提到, 可以用数组获取子进程的pid,然后传值进行等待,虽然效果不一定很好,但这也算是一个解决办法,为什么不用全局变量获取子进程的退出信息,而是采用写入的方式进行传参获取呢?

这是因为进程之间具有独立性,父进程无法直接获取子进程的退出信息,比如status我们设置为0,父子进程看到的status值就都为0,此时我们获取到了子进程的退出码,将子进程的退出码写入status变量,此时会发生写时拷贝,子进程看到的是我自己写的新值,而父进程看到的还是0。父子进程代码共享,但数据不一定相同

而父进程通过fork,返回的id是子进程的pid,子进程返回的id为0,已经写时拷贝过了,因此可以获取。

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

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

相关文章

IPC资源在linux内核中如何管理

1.先看各个通信的接口 1.共享内存接口 2.消息队列接口 3.信号量接口 2.管理他们的结构体&#xff1a; 其实管理他们的是一个数组&#xff0c;和open返回的fd差不多&#xff0c;shmid&#xff0c;msqid,semid的大小都是这个数组的下标。那数组的结构是什么呢&#xff1f; 然后…

UniApp项目处理小程序分包

目前 uniApp也成为一种 App端开发的大趋势 因为在目前跨端 uniApp可以说相当优秀 可以同时兼容 H5 PC 小程序 APP 的技术 目前市场屈指可数 那么 说到微信小程序 自然就要处理分包 因为微信小程序对应用大小限制非常铭感 限制在2MB 超过之后就会无法真机调试与打包 不过需要注…

前端学习第四天-css提升

达标要求 掌握css复合选择器 块级元素和行内元素及行内块的区别? 哪些元素是块元素,行内元素及行内块元素? 熟练掌握display的用法 能够说出css三大特性 熟练运用背景样式 1. CSS复合选择器 复合选择器是由两个或多个基础选择器&#xff0c;通过不同的方式组合而成的…

使用 Docker 部署 MrDoc 在线文档管理系统

1&#xff09;MrDoc 介绍 MrDoc 简介 MrDoc 觅思文档&#xff1a;https://mrdoc.pro/ MrDoc 使用手册&#xff1a;https://doc.mrdoc.pro/p/user-guide/ MrDoc 可以创建各类私有化部署的文档应用。你可以使用它进行知识管理、构建团队文库、制作产品手册以及在线教程等。 Mr…

抖音视频批量采集软件|视频评论下载工具

在日常工作中&#xff0c;需要频繁下载抖音视频&#xff0c;但逐个复制分享链接下载效率太低&#xff1f;别担心&#xff01;我们推出了一款专业的抖音视频批量采集软件&#xff0c;基于C#开发&#xff0c;满足您的需求&#xff0c;让您通过关键词搜索视频并自动批量抓取&#…

Zookeeper学习2:原理、常用脚本、选举机制、监听器

文章目录 原理选举机制&#xff08;重点&#xff09;情况1&#xff1a;正常启动集群情况2&#xff1a;集群启动完&#xff0c;中途有机器挂了 监听器客户端向服务端写入数据客户端向服务端Leader节点写入客户端向服务端Follower节点写入 Paxos算法&#xff08;每个节点都可以提…

Dynamo幕墙探究系列(四)——Revolve

我们先放一张截图&#xff0c;不再是通过 loft 创建模型&#xff0c;而是通过旋转生成模型&#xff0c;效果如下&#xff0c;今天我们就来聊聊这个模型是怎么生成得。 “旋转”&#xff0c;顾名思义&#xff0c;和 Revit 中创建形状的旋转是一个意思&#xff0c;只是用来旋转的…

【MATLAB】 CEEMDAN信号分解+FFT傅里叶频谱变换组合算法

有意向获取代码&#xff0c;请转文末观看代码获取方式~ 展示出图效果 1 CEEMDAN信号分解算法 CEEMDAN 分解又叫自适应噪声完备集合经验模态分解&#xff0c;英文全称为 Complete Ensemble Empirical Mode Decomposition with Adaptive Noise。 CEEMDAN是对CEEMD的进一步改进…

MySQL 教程 2.4

MySQL UNION 操作符 本教程为大家介绍 MySQL UNION 操作符的语法和实例。 描述 MySQL UNION 操作符用于连接两个以上的 SELECT 语句的结果组合到一个结果集合&#xff0c;并去除重复的行。 UNION 操作符必须由两个或多个 SELECT 语句组成&#xff0c;每个 SELECT 语句的列数…

蓝桥杯倒计时 41天 - KMP 算法

KMP算法 KMP算法是一种字符串匹配算法&#xff0c;用于匹配模式串P在文本串S中出现的所有位置。 例如S“ababac&#xff0c;P“aba”&#xff0c;那么出现的所有位置是13。 在初学KMP时&#xff0c;我们只需要记住和学会使用模板即可&#xff0c;对其原理只需简单理解&#xff…

会员丨这些年开的会员

1、淘宝88VIP-88元/年 要说现在最实惠的会员&#xff0c;肯定是88vip莫属了。88元/年即可拥有&#xff1a; 优酷/芒果年卡&#xff1b; 饿了么年卡&#xff08;每月4张吃货卡&#xff0c;但现在饿了么改的越来越不实惠了&#xff09;&#xff1b; 网易云音乐年费会员&#xf…

NACOS在Windows和Linux下的安装教程

目录 1、Windows安装 1.1、下载安装包 1.2、解压 1.3、端口配置 1.4、启动 1.5、访问 2、Linux安装 2.1、安装JDK 2.2、上传安装包 2.3、解压 2.4、端口配置 2.5、启动 3、Nacos的依赖 1、Windows安装 开发阶段采用单机安装即可。 1.1、下载安装包 在Nacos的Git…

【Python】进阶学习:pandas--query()用法详解

&#x1f4da;【Python】进阶学习&#xff1a;pandas–query()用法详解 &#x1f308; 个人主页&#xff1a;高斯小哥 &#x1f525; 高质量专栏&#xff1a;Matplotlib之旅&#xff1a;零基础精通数据可视化、Python基础【高质量合集】、PyTorch零基础入门教程&#x1f448; 希…

open-spider开源爬虫工具:抖音数据采集

在当今信息爆炸的时代&#xff0c;网络爬虫作为一种自动化的数据收集工具&#xff0c;其重要性不言而喻。它能够帮助我们从互联网上高效地提取和处理数据&#xff0c;为数据分析、市场研究、内容监控等领域提供支持。抖音作为一个全球性的短视频平台&#xff0c;拥有海量的用户…

行业独角兽—Matic Network来临,成就百万富翁的项目!

Matic Network由印度Bangalore及日本超级节点打造 &#xff0c;独创保险仓九仓共振循环模式。 Mtc于2023年初完成了700万美元的种子轮融资&#xff0c; Paradigm领投&#xff0c;a16z、Variant、Solana Ventures和Jump Crypto参投&#xff0c;旨在全方位布局Web3.0的去中心化生…

Java基础数据结构之栈

一.什么是栈 栈是一种特殊的线性表&#xff0c;它只允许在固定的一端进行元素的添加与使用&#xff0c;且遵循先进后出的原则。添加取用元素的一端称为栈顶&#xff0c;另一端称为栈底。出栈和入栈都是操作栈顶元素 二.栈的模拟实现 栈的底层是一个数组 这是里面的成员变量以…

element-ui的 Dialog 对话框背景图片

element-ui的 Dialog 对话框背景图片 效果如图&#xff1a; 代码&#xff1a; &#xff01;&#xff01;&#xff01;注&#xff1a;如果style里有scoped"scoped"会不生效&#xff0c;要单独写个<style></style> <style> .bgc {/* 弹窗样式 */.e…

7.1.1 selenium介绍及安装chromedriver

目录 1. Selenium的用途 2. 安装Selenium库 3. 安装chromedriver 1. 查看谷歌版本号​编辑 2. 找到最新版本及下载 3. 配置环境变量 4. 检测是否配置成功 5. 用python初始化浏览器对象检测&#xff1a; 6. 参考链接 1. Selenium的用途 在前面我们提到&#xff1a;在我…

简单实现Transformer的自注意力

简单实现Transformer的自注意力 关注{晓理紫|小李子}&#xff0c;获取技术推送信息&#xff0c;如感兴趣&#xff0c;请转发给有需要的同学&#xff0c;谢谢支持&#xff01;&#xff01; 如果你感觉对你有所帮助&#xff0c;请关注我。 源码获取&#xff1a;VX关注并回复chatg…

【Vue3】Props的使用详解

&#x1f497;&#x1f497;&#x1f497;欢迎来到我的博客&#xff0c;你将找到有关如何使用技术解决问题的文章&#xff0c;也会找到某个技术的学习路线。无论你是何种职业&#xff0c;我都希望我的博客对你有所帮助。最后不要忘记订阅我的博客以获取最新文章&#xff0c;也欢…