Linux进程控制——Linux进程等待

news2025/1/17 21:50:11

前言:接着前面进程终止,话不多说我们进入Linux进程等待的学习,如果你还不了解进程终止建议先了解:

Linux进程终止


在这里插入图片描述


本篇主要内容:
什么是进程等待
为什么要进行进程等待
如何进程等待

在这里插入图片描述


进程等待

  • 1. 进程等待的概念
  • 2. 进程等待必要性
  • 3. 进程等待的方法
    • 3.1 wait方法
    • 3.2 waitpid方法
  • 4. 获取子进程status
    • 获取子进程退出信息
  • 5. waitpid的第三个参数options
  • 6. 总结拓展


1. 进程等待的概念

首先在开始之前我们提个问题,到底什么是进程等待?

进程等待的概念:

  • 我们通常说的进程等待其实是通过wait/waitpid的方式,让父进程(一般)对子进程进行资源回收的等待过程,父进程必须等待这个子进程结束后,处理它的代码和数据!

2. 进程等待必要性

在了解完进程等待的概念后,新的问题出现了,我们为什么要进行进程等待,进程等待的必要性是什么?

进程等待必要性:

  • 若子进程退出,而父进程对它不管不顾,就可能造成‘僵尸进程’的问题,进而造成内存泄漏。
  • 进程一旦变成僵尸状态,那就刀枪不入,“杀人不眨眼”的kill -9 也无能为力,谁也没有办法杀死一个已经死去的进程。
  • 父进程创建子进程的目的是为了让子进程协助自己完成任务的,而父进程需要知道子进程将任务完成得如何。这就需要通过进程等待的方式,获取子进程的退出信息。

3. 进程等待的方法

3.1 wait方法

我们可以通过系统调用来等待进程:wait函数

wait等待任意一个子进程的退出,如果等待成功他将返回子进程的pid,失败则返回-1

在这里插入图片描述
在这里插入图片描述

我们就用一段代码来看看wait:

   #include<stdio.h>
   #include<unistd.h>
   #include<stdlib.h>
   #include<sys/types.h>
   #include<sys/wait.h>
  
   void Worker()
   {
       int cnt = 5;
      while(cnt)
      {
          printf("i am child, pid: %d, ppid: %d, cnt: %d\n", getpid(), getppid(), cnt--);
          sleep(1);
      }
  }
  
  int main()
  {                                                                                                                                                                                      
      pid_t id = fork();
  
      if(id == 0)
      {
          // child
          Worker();
          exit(0);
      }
      else{
          sleep(10);
          // father
          pid_t rid = wait(NULL);
          if(rid == id)
          {
              printf("wait success, pid: %d\n", getpid());
          }
      }
  
      return 0;
  }

进程等待:wait函数

我们通过视频发现:进程等待是可以回收子进程僵尸状态的


然后我们将父进程sleep()取消,看看在子进程退出之前父进程在干什么

   #include<stdio.h>
   #include<unistd.h>
   #include<stdlib.h>
   #include<sys/types.h>
   #include<sys/wait.h>
  
   void Worker()
   {
       int cnt = 5;
      while(cnt)
      {
          printf("i am child, pid: %d, ppid: %d, cnt: %d\n", getpid(), getppid(), cnt--);
          sleep(1);
      }
  }
  
  int main()
  {                                                                                                                                                                                      
      pid_t id = fork();
  
      if(id == 0)
      {
          // child
          Worker();
          exit(0);
      }
      else{
          // father
          printf("wait before:\n");
          pid_t rid = wait(NULL);
          printf("wait after:\n");
          if(rid == id)
          {
              printf("wait success, pid: %d\n", getpid());
          }
          sleep(10);
      }
      return 0;
  }

观察父进程等待过程

通过这个视频我们又能发现两个进程一起运行,但是在子进程没有退出之前,父进程一直在wait上等待,并且并没有出现子进程僵尸状态而是直接回收了。

结论:如果子进程根本就没有退出,父进程必须在wait上进行阻塞等待。直到子进程僵尸,wait自动回收返回。


3.2 waitpid方法

waitpidwait都是等待进程。waitpid可以指定等待一个进程,且有三个参数
在这里插入图片描述
在这里插入图片描述


4. 获取子进程status

父进程想要知道子进程的退出信息,也就是退出码和退出信号,就要用到输出型参数status

  • wait和waitpid,都有一个status参数,该参数是一个输出型参数,由操作系统填充。
  • 如果传递NULL,表示不关心子进程的退出状态信息。
  • 否则,操作系统会根据该参数,将子进程的退出信息反馈给父进程。
int main()
  {                                                                                                                                                                                      
      pid_t id = fork();
  
      if(id == 0)
      {
          // child
          Worker();
          exit(10); // 设置成10方便观察现象
      }
      else{
          // father
          printf("wait before:\n");
          int status = 0;
          pid_t rid = waitpid(id, &status, 0);
          printf("wait after:\n");
          if(rid == id)
          {
              printf("wait success, pid: %d, status: %d\n", getpid(), status);
          }
      }
      return 0;
  }

在这里插入图片描述

在这里插入图片描述

我明明将exit的退出结果设置成10,但是为什么他的status会是2560呢?

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

在这里插入图片描述
在这里插入图片描述
因此我们在研究status时,不能整体使用status!!!


获取子进程退出信息

因为我们知道了status不能整体使用因此我们要进行位操作:


exit sig: status&0x7f //获取信号

exit code: (status>>8)&0xff //获取退出结果

  • 当我们的程序异常了,exit code 将无任何意义
  • exit sig : 0则代表没有收到信号
  • 手动杀掉子进程也会获取到信号

但是如果我们每次提取退出信息都要使用繁琐的位运算,这很不方便,因此系统给我们做了一个简单的封装

status:

  • WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)
  • WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)
int main()
{
    pid_t id = fork();
  
    if(id == 0)
    {
        // child
        Worker();
        exit(1);
    }
    else{
        // sleep(10);
        // father
        printf("wait before:\n");
        //pid_t rid = wait(NULL);                                                             
        int status = 0;                              
        pid_t rid = waitpid(id, &status, 0);   
        printf("wait after:\n");
        if(rid == id)                       
        {                           
            // 不能对status整体使用
            //printf("wait success, pid: %d, rpid: %d, exit sig: %d, exit code: %d\n",getpid(), rid, status&0x7f, (status>>8)&0xff);
            if(WIFEXITED(status))
            {
                  printf("child process normal quit, exit code: %d\n", WEXITSTATUS(status));
            }                                                    
            else{                                         
                printf("child process quit exept!!!\n");
            }
        }
    }                                                                                                                                                                                  
    return 0;
}            

在这里插入图片描述


当我们要获取多个进程的调度信息时,我们给每个进程都要一个编号,我们来观察一下进程是怎样调度的

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

int main()
{
    for(int i = 0; i < n; i++)
    {
        pid_t id = fork();
        if(id == 0)                 
        {              
            Worker(i);                  
            exit(i);
        }                                                                                                                                                                              
    }                                                                                                                 
    // 等待多个子进程                 
    for(int i = 0; i < n; i++)                        
    {                                         
        int status = 0;                                                                                                   
        pid_t rid = waitpid(-1, &status, 0); //pid > 0, -1:等待任意一个进程
        if(rid > 0)
        {
            printf("wait child %d success, exit code: %d\n", rid, WEXITSTATUS(status));
        }
    }
    return 0;
 }

观察进程调度顺序

我们发现明明是按顺序创建的进程,但是在调度时却没有顺序可言,终止的时候也没有顺序,因为进程在调度完全由调度器说的算,所以进程调度的先后我们并不确定,这点在前面我们也提到过。


5. waitpid的第三个参数options

在使用waitpid的第三个参数时,前面我们提到设为0则是默认阻塞等待状态,必须等待子进程的退出,当时如果我们要做自己的事我们就不能使用0而是使用:WNOHANG

options:

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

**父进程在非阻塞等待时,因为子进程没有结束,就跑去做自己的事情了,但是又要继续等待,所以往往伴随着重复调,轮询,也就是基于非阻塞轮询的等待方案!
**


我们来直接验证一下非阻塞等待:

void Worker(int cnt)
{
     printf("i am child, pid: %d, cnt: %d\n", getpid(), cnt);
     sleep(1);
}
int main()
{
    pid_t id = fork();
    if(id == 0)
    {
       // 子进程
       int cnt = 5;
       while(cnt)
       {
           Worker(cnt);
           sleep(1);
           cnt--;
       }
       exit(1);
 }
       // 父进程
       while(1)
       {
           int status = 0;
           pid_t rid = waitpid(id, &status, WNOHANG);
           if(rid > 0)                                                                                                                                                                    
           {
              // 等待成功
              printf("wait success, child quit, exit code: %d, exit sig: %d\n", (status>>8)&0xff, status&0x7f);
              break;
           }
           else if(rid == 0)
           {
              // 等待成功,但子进程并未退出
              printf("wait again, child alive, do other thing\n");                                                                                                                       
              sleep(1);
           }
           else{
              // 等待失败
              printf("wait failed\n");
              break;
          } 
       } 
     return 0;
  }

在这里插入图片描述
我们可以看到非阻塞等待可以让我们的父进程在等待时,做自己的事!


6. 总结拓展

拓展一:父进程如何得知子进程的退出信息
在这里插入图片描述
父进程调用wait()/waitpid()来获取子进程的退出信息,调用的接口就传入了一个status参数,而父进程中存在着一个statusp的指针。

而子进程在退出时,操作系统就会将退出信号和退出码写到子进程的PCD中

int exit_code;
int exit_signal

而退出信号和退出码将会写到这两个变量中,
当我们调用系统调用时,只需要将这两个变量组合写入到变量里

*statusp = (exit code<<8)| exit siganl

这样父进程就获取到了子进程的退出信息


拓展二:我们为什么不用全局变量获取子进程的退出信息而用系统调用?

这个就是因为进程具有独立性,父进程无法直接获得子进程的退出信息


总结:

进程等待确实非常有用,它既可以回收僵尸进程,避免造成内存泄漏,也能让父进程能够获取到子进程的退出信息,进程等待我们就先了解这么多,进程控制马上就到了我们的最后一步——进程替换,让我们来期待下一篇!

谢谢大家支持本篇到这里就结束了
在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

“等保测评实施策略:保障企业信息安全合规“

等保测评&#xff0c;即网络安全等级保护测评&#xff0c;是企业保障信息安全合规的重要实施策略。以下是企业实施等保测评的一些关键策略&#xff1a; 理解等保测评的重要性&#xff1a; 等保测评有助于企业识别和评价信息系统的安全性能&#xff0c;提供改进建议&#xff0c;…

user32.dll怎么修复?几种修复user32.dll丢失的详细步骤

user32.dll怎么修复&#xff1f;几种修复user32.dll丢失的详细步骤user32.dll是Windows操作系统中的一个核心动态链接库文件&#xff0c;它主要负责处理图形用户界面&#xff08;GUI&#xff09;相关的功能。这个文件是Windows API&#xff08;应用程序编程接口&#xff09;的一…

css超出部分省略(单行、多行,多种方法实现)

HTML <p class"text">这是一行测试数据,这是一行测试数据,这是二行测试数据,这是一行测试数据,这是三行测试数据,这是四行测试数据</p>1.单行 .text{width: 200px;border: 1px solid #000000;white-space: nowrap; /* 控制元素不换行 */overflow: hi…

骨传导耳机哪个品牌值得入手?盘点5款高人气热门机型推荐!

随着人们对健康生活方式的追求和户外运动的普及&#xff0c;骨传导耳机的需求也日益增长。然而&#xff0c;随着骨传导耳机的热度增加&#xff0c;市场上也开始出现一些低质量的骨传导耳机产品&#xff0c;这些劣质耳机在音质、佩戴舒适度或安全性上存在各种不足&#xff0c;甚…

Matlab如何批量导出多张高质量论文插图?科研效率UpUp第9期

上一期文章中&#xff0c;分享了Matlab导出高质量论文插图的方法&#xff08;Matlab如何导出高质量论文插图&#xff1f;科研效率UpUp第8期&#xff09;。 进一步&#xff0c;假如我们想要批量导出多张高质量无变形论文插图&#xff0c;该如何操作呢&#xff1f; ​也很简单&…

【MySQL01】【 Explain 命令详解】

文章目录 一、前言二、Explain 概览三、Explain 详解1. id2. select_type3. table4. type5. possible_keys6. key7. key_len8. ref9. rows10. filtered11. extra 列 四、补充1. EXPLAIN 扩展1.1 Extend EXPLAIN1.2 JSON 格式的执行计划 2. Intersection、Union、Sort-Union 索引…

添砖Java之路(其八)——继承,final关键字

继承&#xff1a; 意义&#xff1a;让类于类之间产生父类于子类的关系&#xff0c;子类可以直接使用父类中的非私有成员(包括方法与成员变量) 。 extends关键字就是定义声明父类。 格式&#xff1a;public class 子类 extends 父类。 对于基础的我就不赘述了&#xff0c;我…

【接口测试_03课_-接口自动化思维梳理及Requests库应用】

一、通过代码&#xff0c;实现Jmeter 1、项目要放在虚拟环境里面&#xff0c;解释器要使用虚拟环境的 上面是虚拟环境&#xff0c;下面是系统环境。2选一 venv目录 查看当前虚拟环境已存在的依赖包 2、安装Requests依赖包 1&#xff09;安装命令 pip install requests 如果…

python输出希腊字母

有时候在绘制一些函数图像时&#xff0c;需要坐标轴和图例显示希腊字母 plt.xlabel(r’ ϵ \epsilon ϵ’)

AI绘画Stable Diffusion 模型辅助神器之 Lora 提示词助手

大家好&#xff0c;我是向阳 小伙伴们也知道&#xff0c;大多数的 Lora 模型都是有触发词的&#xff0c;而且每个模型的触发词都不一样。 模型少的时候还行&#xff0c;模型多起来了&#xff0c;还得一个一个去翻以前的笔记&#xff0c;真的挺麻烦。要是漏了没记&#xff0c;…

GPT-4o正式发布;零一万物发布千亿参数模型;英国推出AI评估平台

OpenAI 正式发布 GPT-4o 今天凌晨&#xff0c;OpenAI 正式发布 GPT-4o&#xff0c;其中的「o」代表「omni」&#xff08;即全面、全能的意思&#xff09;&#xff0c;这个模型同时具备文本、图片、视频和语音方面的能力&#xff0c;甚至就是 GPT-5 的一个未完成版。 并且&…

emp.dll文件丢失荒野大镖客,怎么快速修复emp.dll

缺失或损坏的 DLL 文件是会导致系统或软件故障的&#xff0c;DLL&#xff08;动态链接库&#xff09;文件是 Windows 操作系统中至关重要的一部分&#xff0c;它们允许多个程序共享代码和资源&#xff0c;从而减少内存占用和增强系统性能。然而&#xff0c;当EMP.dll文件丢失或…

JAVA中类和对象(承接上次的补充)

目录&#xff1a; 一.static修饰成员方法 二.static成员变量初始化 三.代码块 一.static修饰成员方法: 1.一般类中的数据成员都设置为 private &#xff0c;而成员方法设置为 public &#xff0c; 问&#xff1a;那设置之后&#xff0c;Student类中&#xff0c;被Student修饰…

食家巷传统面点积极响应中国品牌日,打造国货潮牌

2024 年中国品牌日活动以“中国品牌&#xff0c;世界共享&#xff1b;国货潮牌&#xff0c;品筑未来”为主题&#xff0c;旨在推动中国品牌的发展和国际化&#xff0c;展示国货潮牌的魅力和创新。食家巷传统面点品牌积极响应活动号召&#xff0c;以实际行动助力中国品牌的崛起。…

Databend 开源周报第 144 期

Databend 是一款现代云数仓。专为弹性和高效设计&#xff0c;为您的大规模分析需求保驾护航。自由且开源。即刻体验云服务&#xff1a;https://app.databend.cn 。 Whats On In Databend 探索 Databend 本周新进展&#xff0c;遇到更贴近你心意的 Databend 。 了解 Databend …

2024精美UI小程序打印系统源码 PHP后端 附搭建教程+功能脑图

内容目录 一、详细介绍二、效果展示1.部分代码2.效果图展示 三、学习资料下载 一、详细介绍 后端安装说明&#xff1a; 测试环境&#xff1a;NginxPHP7.4MySQL5.6 PHP安装扩展&#xff1a;sg11 网站运行目录设置为&#xff1a;/public 网站伪静态规则设置为&#xff1a;think…

计算机网络复习-应用层

概述 传输层以及以下的层提供完整的通信服务&#xff0c;不需要管传输&#xff0c;只需要往上对接用户即可。应用层是面向用户的一层 定义应用间通信的规则 应用进程的报文类型 (请求报文、应答报文)报文的语法、格式应用进程发送数据的时机、规则 DNS详解 DNS&#xff1a…

数据结构与算法-排序算法2-选择排序

目录 1.选择排序&#xff1a; 1.介绍&#xff1a; 2.动态图解 3.举例 4.小结选择排序规则 5.选择排序代码 6.运行时间 代码&#xff1a; 运行结果&#xff1a; 1.排序算法简介 排序也称为排序算法。排序是将一组数据依据指定的顺序进行排列的过程。 2.常见的排序算法…

Building3D An Urban-Scale Dataset and Benchmarks 论文阅读

文章主页 Building3D 任务 提出了一个城市规模的数据集&#xff0c;由超过 16 万座建筑物以及相应的点云、网格和线框模型组成&#xff0c;覆盖爱沙尼亚的 16 个城市&#xff0c;面积约 998 平方公里。 动机 现有的3D建模数据集主要集中在家具或汽车等常见物体上。缺乏建…

网络安全快速入门(十)MySQL拓展操作

10.1.0前言 前面我们已经对用户操作以及库&#xff0c;表操作有了基础的认识&#xff0c;接下来我们来在之前已经学过的一些操作进行进一步拓展&#xff0c;本章我们主要了解以下几个知识点&#xff1a; 数据库设计方法视图存储过程事务 我们开始本章的内容吧 10.2 数据库设计方…