【Linux】进程等待


文章目录

  • tips
  • 一、进程等待是什么?
  • 二、为什么要有进程等待?
  • 三、怎么做到进程等待?
    • 先看看什么是进程等待
    • wait和waitpid
      • status参数
      • options参数
        • 非阻塞轮询
    • 进程等待的原理
  • 总结



tips

下面的代码可以循环检测进程。

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

一、进程等待是什么?

通过系统调用wait/waitpid,来对子进程进行状态检测和回收。

二、为什么要有进程等待?

  • 1.僵尸进程无法被杀死,需要通过进程等待来杀掉它,从而解决内存泄露的问题。 ——这是必须解决的
  • 2.需要通过进程等待,来获得子进程的退出情况。也就是我要知道布置给它的任务做的怎么样了。——这是可选做的

进程等待的重点就是为了解决上述两个问题。

三、怎么做到进程等待?

先看看什么是进程等待

  7 int main()
  8 {
  9 
 10      pid_t id = fork();
 11      if(id < 0)
 12      {
 13          perror("fork");
 14          return 0;
 15      }
 16      else if(id == 0)
 17      {
 18          //child
 19          int cnt = 5;
 20          while(cnt)
 21          {
 22              printf("I am child,pid : %d , ppid : %d\n",getpid(),getppid());
 23              sleep(1);
 24              cnt--;
 25          }
 26 
 27          exit(0);
 28      }
 29      else
 30      {                                                                                                                                                                                     
 31          //parent
 32          int cnt = 10;
 33          while(cnt)
 34          {
 35              printf("I am parent,pid : %d , ppid : %d\n",getpid(),getppid());
 36              sleep(1);
 37              cnt--;
 38          }
 39 
 40          pid_t ret = wait(NULL);
 41          if(ret == id)
 42          {
 43              printf("wait success,ret = %d\n",ret);
 44          }
 45 
 46          sleep(5);
 47      }
 48 }

上面代码的意思是:
先创建一个子进程,父子进程同时跑5s,前5s都处在阻塞状态,即S状态。
中间5秒子进程退出,等待父进程来回收,此时子进程变成僵尸状态,即Z状态。
后5秒父进程通过wait等待子进程退出后,回收子进程,此时只有父进程还在运行,
随后父进程也退出。

在这里插入图片描述

下面是循环创建10个子进程:

  7 void Runchild()
  8 {
  9     int cnt = 5;
 10     while(cnt)
 11     {
 12         printf("i am child, pid : %d,ppid : %d\n",getpid(),getppid());
 13         sleep(1);
 14         cnt--;
 15     }
 16 }
 17 
 18 int main()
 19 {
 20     int i = 0;
 21     //创建10个子进程
 22     for(;i<10;i++)
 23     {
 24         pid_t id = fork();
 25         if(id == 0)
 26         {
 27             Runchild();
 28             exit(0);
 29         }
 30     }
 31 
 32     sleep(10);
 33 
 34     //目前来说,进程等待是必须的
 35     for(i=0;i<10;i++)                                                                                                                                                                      
 36     {
 37         pid_t ret = wait(NULL);
 38         printf("wait success: %d\n",ret);
 39     }
 40 
 41     sleep(5);
 42     return 0;
 43 }

前5秒执行代码,创建10个子进程,父子进程同时运行。
中间5秒,10个子进程退出,变成僵尸状态,等待父进程回收
后5秒,父进程调用10次wait系统调用,对10个僵尸进程进行回收。
回收5秒后,父进程退出。

总结:目前来说,进程等待是必须的,这样才能杀掉僵尸状态,回收资源,防止内存泄露。


问题1:
如果父进程一直在等待子进程,而子进程一直不退出呢?

如果父进程等不到子进程退出,父进程就会一直等,wait不返回结果,父进程就会一直处于阻塞状态。

wait和waitpid

在这里插入图片描述

status参数

wait函数只需要传入一个参数:status,代表进程的退出状态,一般传入的时候是一个整数,整数是32个比特位,一般只考虑低16位。

在这里插入图片描述

其中,退出core dump后面会讲,进程的退出信号可以查询:

通过 kill -l命令查询到到有64个退出信号,由status变量的0~7个比特位表示。

在这里插入图片描述

进程的退出码由8~15个比特位表示。
在这里插入图片描述

其中0就表是进程正常运行,结果正确。


问题:能不能自己创建一个全局变量status,子进程退出时将status变量设置成不同的退出码,代表不同的退出状态返回给父进程呢?

答案是不可能的。**因为父子进程之间具有独立性。**当子进程要修改status时,会发生写时拷贝,父子进程的资源互相独立,子进程返回的status父进程是拿不到的,所以只能通过系统调用让操作系统帮父进程从子进程中拿资源。

通过父进程对子进程进行等待,将status参数传入waitpid中,获取子进程的退出信息和退出码。

if(id > 0)
{
	int status=0; // 低16位有效
	pid_t ret = waitpid(id,&status,0);
	if(ret == id)
	{
	    //低七位是退出信号,次低八位是退出码
	    printf("wait success! id = %d , exit sig = %d, exit code = %d\n",ret,status&0x7F,(status>>8)&0xFF);
	}
}

核心代码如上:

status&0x7F,即可获取到子进程的退出信号
在这里插入图片描述
通过status的次低8位,获取到子进程的退出码。
(status>>8)&0xFF

其实,库里面还提供了两个宏,来代替上面的stauts&0x7F, 和(status>>8)&0xFF

  • 1.WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出) ——相当于把status&0x7F再加上一个逻辑取反。

  • 2.WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)——相当于(status>>8)&0xFF

 if(ret == id)
 {
     //低七位是退出信号,次低八位是退出码
     //printf("wait success! id = %d , exit sig = %d, exit code = %d\n",ret,status&0x7F,(status>>8)&0xFF);
     if(WIFEXITED(status))
     {
         printf("父进程等待它的子进程成功,退出码为:%d\n",WEXITSTATUS(status));
     }
 }
 else
 {
     printf("父进程等待它的子进程失败!\n");
 }

核心代码如上,进入if说明父进程等待的是它的子进程成功。
进入else,说明父进程等待的不是它自己的子进程。

所以,父进程只有等待它自己创建的子进程时才会成功。


总结:

  • 1.只要通过wait/waitpid函数的status参数低7位判断是否为0,就能知道进程是否跑完。
  • 2.只要通过8~15位判断进程的退出码是多少,就知道进程运行结果如何。

这就对应了进程的退出情况无非只有三种:

  • 1.进程正常退出,结果正确
  • 2.进程正常退出,结果不正确
  • 3.进程异常退出

从此,就能通过waitpid,等待对应的进程,进而获取到对应进程的退出情况。

options参数

options参数的意思就是,让父进程选择什么样的方式等待子进程退出!

下面提供了两种等待方式,阻塞等待和非阻塞等待!

非阻塞轮询

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

WNOHANG是一个宏,就表示非阻塞等待。

先讲讲什么是轮询:

将近期末考试,小吴是一个努力型学霸,小邓是一个摆烂型学渣,老师说:明天要考c语言了!
小邓一听,顿时慌了,下课后马上打电话给小吴,对小吴说:“小吴,你的笔记和你这个人可以借我用一下吗?”
小吴一听就知道了,这是小邓想要我教他呢!
小吴说:“好,你等等,我在复习,等会就下去找你。”
小邓听完就挂电话了,过了10秒中,小邓又打电话给小吴:“你好了没。”
小吴说:“还没。” 啪的一声,小邓挂断了电话。又过了十秒,又打电话给小吴:“你好了没。” “还没。” 就这样,打了20几个电话,小吴说:“好了,我现在下去找你”。

这个每隔一段时间小邓就打电话给小吴询问情况的过程就叫做轮询!!!

而什么叫做非阻塞轮询呢?

在小邓不断打电话给小吴询问情况的过程中,小邓学聪明了,傻等这也不是办法,所以小邓拿着一本c语言的教材在看,边看边等。意味着小邓在等待的过程中,能够做自己的事情!!!

这就意味着非阻塞!!!

所以,两者结合起来,就是非阻塞轮询!!!

小吴就相当于操作系统,小邓就相当于父进程,父进程在不断向操作系统询问等待返回结果的过程,就是轮询的过程。
而在轮询时父进程可以做自己的事情,就叫做非阻塞轮询!!!

总结:WNOHANG等待方式就是非阻塞等待方式,如果等待的子进程没有结束,就返回0,如果等待成功,返回子进程的id。

int main()
{
    pid_t id = fork();

    if(id < 0)
    {
        perror("fork");
        exit(0);
    }
    else if(id == 0)
    {
        //child
        int cnt = 10;
        // int a = 10;
        // a/=0;
        while(cnt--)
        {
            printf("i am a child, pid %d, ppid %d \n",getpid(),getppid());
            sleep(1);
        }
        exit(11);
    }
    else 
    {
        //parent
        int cnt = 5;
        while(cnt--)
        {
            printf("i am a parent, pid %d, ppid %d \n",getpid(),getppid());
            sleep(1);
        }

        while(1)  //轮询
        {
            int status=0; // 低16位有效
            pid_t ret = waitpid(id,&status,WNOHANG); //非阻塞等待
            //pid_t ret = waitpid(id,&status,0); //阻塞等待
            //阻塞等待,父进程什么都做不了,只能在这里死等着子进程

            if(ret < 0) //等待失败
            {   
                printf("等待子进程失败!\n");
                break;
            }

            //等待成功
            else if(ret > 0)
            {
                //低七位是退出信号,次低八位是退出码
                //printf("wait success! id = %d , exit sig = %d, exit code = %d\n",ret,status&0x7F,(status>>8)&0xFF);
                if(WIFEXITED(status))
                {
                    printf("父进程等待它的子进程成功,退出码为:%d\n",WEXITSTATUS(status));
                }
                break;
            }

            else
            {
                printf("子进程还未退出,再等等....\n");
                //等的过程父进程可以做自己的事情
                int a = 10;
                int b = 20;
                int c = a+b;
                printf("父进程在进行相加运算,结果为:%d\n",c);
            }

            //每隔1秒等待1次
            sleep(1);
        }
        
    }
    return 0;
}
pid_t ret = waitpid(id,&status,WNOHANG); 

这段代码,就是父进程在等待子进程时采用的是非阻塞等待(WNOHANG)
不断执行while循环就是轮询。

pid_t ret = waitpid(id,&status,0); 

这段代码,就是阻塞等待,如果子进程未退出,父进程就会在这里傻傻地等待子进程退出。自己什么事情都做不了。

进程等待的原理

父进程通过调用操作系统提供的系统调用waitpid,然后由操作系统接收到父进程的申请,去查询父进程对应的子进程的状态。
如果子进程处于s状态,则操作系统会将子进程的状态结果返回给父进程。
如果子进程处于z状态,则操作系统会将子进程的退出码exit_code,退出信号exit_signal拿出来返回给父进程。父进程收到信息后就知道该如何做下一步动作。

在这里插入图片描述


问题:为什么父进程不直接去子进程中获取子进程的运行状况呢?

因为操作系统不相信任何人!!!

这就比如说:A学校的有一个网络空间安全方面的学生特别厉害,B学校的校长想邀请A学校的那个学生帮助B学校参加一项网络空间安全的比赛,B校长直接找到A学校的那名学生叫他去参加比赛。
这是不能这样做的,因为B校长想要找A学校的学生,还得问过A校长才行!!!

操作系统对父进程也是如此,父进程不能直接获取子进程的数据,必须通过操作系统这一媒介!!!

因为要是父进程修改了子进程的数据了呢???

所以,操作系统不相信任何人!!!

总结

这篇文章详细介绍了进程等待的各种细节。

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

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

相关文章

ASK、PSK、FSK的调制与解调

ASK、PSK、FSK的调制与解调 本文主要涉及数字信号的调制与解调&#xff0c;内容包括&#xff1a;2ASK、2PSK、2FSK的调制与解调以及频谱分析 关于通信原理还有其他文章可参考&#xff1a; 1、信息量、码元、比特、码元速率、信息速率详细解析——实例分析 2、模拟系统的AM信号的…

在个人简历中为什么要写个人优势?

问题描述&#xff1a; 在个人简历中为什么要写个人优势&#xff1f; 解决方案&#xff1a; 在个人简历中写个人优势的目的是突出你作为求职者的独特优势和价值&#xff0c;一段精简且亮眼的介绍能够快速抓住HR的眼球&#xff0c;判断你与岗位的契合度&#xff0c;进而对你产生…

2023年阿里云服务器最新日常价、活动价格、可使用优惠券金额及券后价格参考

阿里云服务器最新实际购买价格参考&#xff0c;轻量应用服务器2核2G3M带宽配置日常价720.00元/1年&#xff0c;最新活动价格为87元/1年&#xff0c;订单满300元以上即可使用满减优惠券&#xff0c;例如经济型e实例2核4G2M带宽日常价格为1802.40元&#xff0c;最新的活动价格为8…

归并排序详解

&#x1f389;个人名片&#xff1a; &#x1f43c;作者简介&#xff1a;一名乐于分享在学习道路上收获的大二在校生&#x1f43b;‍❄个人主页&#x1f389;&#xff1a;GOTXX &#x1f43c;个人WeChat&#xff1a;ILXOXVJE&#x1f43c;本文由GOTXX原创&#xff0c;首发CSDN&a…

彭州市民政局“四个聚焦” 推动未成年人保护工作

聚焦机制完善。以“六大保护”为主导&#xff0c;聚焦“27&#xff08;市级部门&#xff09;13&#xff08;镇、街道&#xff09;”整体联动&#xff0c;定期开展信息交流会、跨部门协同工作培训会等活动&#xff0c;不断健全协调机制、完善协同体系&#xff0c;进一步提升全市…

监控和数据采集软件架构和详细设计

介绍 监控和数据采集软件通过提供实时监控、数据收集和分析功能&#xff0c;在各个行业中发挥着至关重要的作用。这些软件应用程序可帮助企业收集有价值的见解、优化流程并做出明智的决策。在本文中&#xff0c;我们将探讨监测和数据采集软件的软件架构、编程技术和详细设计规范…

SpringBoot3基础特性

SpringBoot3基础特性 SpringApplication 自定义banner 类路径添加banner.txt或设置spring.banner.location就可以定制banner推荐网站:Spring Boot banner在线生成工具&#xff0c;制作下载英文banner.txt,修改替换banner.txt文字实现自定义。 提示&#xff1a; 可以通过修改配…

【C++ 学习 ㊱】- 智能指针详解

目录 一、为什么需要智能指针&#xff1f; 二、智能指针的原理及使用 三、auto_ptr 3.1 - 基本使用 3.2 - 模拟实现 四、unique_ptr 4.1 - 基本使用 4.2 - 模拟实现 五、shared_ptr 5.1 - 基本使用 5.2 - 模拟实现 六、weak_ptr 6.1 - shared_ptr 的循环引用问题 …

Amazon Bedrock | 大语言模型CLAUDE 2体验

这场生成式AI与大语言模型的饥饿游戏&#xff0c;亚马逊云科技也参与了进来。2023年&#xff0c;亚马逊云科技正式发布了 Amazon Bedrock&#xff0c;是客户使用基础模型构建和扩展生成式AI应用程序的最简单方法&#xff0c;为所有开发者降低使用门槛。在 Bedrock 上&#xff0…

【PG】PostgreSQL 预写日志(WAL)、checkpoint、LSN

目录 预写式日志&#xff08;WAL&#xff09; WAL概念 WAL的作用 WAL日志存放路径 WAL日志文件数量 WAL日志文件存储形式 WAL日志文件命名 WAL内容 检查点&#xff08;checkpoint&#xff09; 1 检查点概念 2 检查点作用 触发检查点 触发检查点之后数据库操作 设置合…

Spark SQL 每年的1月1日算当年的第一个自然周, 给出日期,计算是本年的第几周

一、问题 按每年的1月1日算当年的第一个自然周 (遇到跨年也不管&#xff0c;如果1月1日是周三&#xff0c;那么到1月5号&#xff08;周日&#xff09;算是本年的第一个自然周, 如果按周一是一周的第一天) 计算是本年的第几周&#xff0c;那么 spark sql 如何写 ? 二、分析 …

C++实现查找连通域

目录 一、概述 1.1、四连通域算法 1.2、八连通域算法 1.3、种子填充法 二、代码 一、概述 图像处理中&#xff0c;查找连通域的算法是图像分割的重要方法之一。它能够将一幅图像分成若干个不重叠的区域&#xff0c;每个区域内部像素具有相似的性质&#xff0c;而不同区域…

重磅:RHCA架构师新班要开课啦:《OpenShift 企业管理(DO280)》

OpenShift 即将开班 想了解的可提前咨询 课程介绍 学习如何安装、配置和管理实例OpenShift企业版管理 (DO280) 旨在帮助系统管理员为安装、配置和管理红帽OpenShift企业版实例做好准备。OpenShift企业版是一款红帽的平台即服务(PaaS)产品&#xff0c;通过使用容器技术为各类…

Linux Zabbix企业级监控平台+cpolar实现远程访问

文章目录 前言1. Linux 局域网访问Zabbix2. Linux 安装cpolar3. 配置Zabbix公网访问地址4. 公网远程访问Zabbix5. 固定Zabbix公网地址 前言 Zabbix是一个基于WEB界面的提供分布式系统监视以及网络监视功能的企业级的开源解决方案。能监视各种网络参数&#xff0c;保证服务器系…

Linux系统上配置MySQL自动备份

1、编写Shell脚本&#xff0c;并保存为.sh文件 #!/bin/bash# 获取当前日期和时间 current_date$(date %Y%m%d) current_time$(date %H%M%S)# 设置备份文件名 path"/usr/local/mysql5.7/bak" bakFileName"dbname_backup_${current_date}_${current_time}.sql&qu…

ChineseChess.2023.11.13.01

中国象棋残局模拟器ChineseChess.2023.11.13.01

树木二维码怎么生成

众所周知&#xff0c;二维码在当今社会已经普及应用。而制作树木二维码也开始受到人们的关注。那么&#xff0c;如何制作树木二维码呢&#xff1f; 树木二维码管理系统的功能 1、基本信息查看&#xff1a;为每棵树木生成唯一的二维码&#xff0c;该二维码扫码后可以了解树木的种…

Java:异常

基本概念 在Java中将程序执行过程中发生的不正常行为称为异常 常见异常 1.算术异常 这一行告诉你异常发生的对应程序和位置 当程序出现异常后&#xff0c;将不会继续执行异常后的代码 这里异常后的abcd不会再打印 2.数组越界异常 3.空指针异常 异常体系结构 上图中Excepti…

C/C++:在#define中使用参数

文章目录 在#define中使用参数参考资料 在#define中使用参数 在#define中使用参数可以创建外形和作用与函数类似的类函数宏。带有 参数的宏看上去很像函数&#xff0c;因为这样的宏也使用圆括号。类函数宏定义的圆 括号中可以有一个或多个参数&#xff0c;随后这些参数出现在替…

RestCloud AppLink已支持的数据源有哪些?

RestCloud AppLink是什么&#xff1f; 首先&#xff0c;我们需要了解RestCloud AppLink是什么&#xff0c;AppLink是一款由RestCloud公司推出的超级应用连接器。不需要开发&#xff0c;零代码&#xff0c;低成本即可快速打通数百款应用之间的数据。通过流程搭建&#xff0c;可…