父子进程之间的等待(wait和waitpid的介绍+原理),status的介绍+恢复退出码(位运算+宏),非阻塞等待(宏),signal查看

news/2025/2/21 5:49:51/

目录

父子进程之间的等待

介绍

为什么要有等待

内存泄漏

如何等待

介绍

pid_t wait (int* status)

介绍

status指针

示例

​编辑

pid_t waitpid (pid_t pid,int* status,int options)

pid

 options

WNOHANG -- 非阻塞等待

示例

status

查看status

status问题

正常退出时,恢复退出码

位运算

进程崩溃,查看signal

引入

kill指令

示例 -- 野指针

示例 -- kill -9

为什么一定要通过上面的这些函数拿到退出信息

那么,这些问题wait / waitpid 是如何解决的呢?


父子进程之间的等待

介绍

通常用于实现协作和同步,以确保父进程等待子进程完成 / 等待子进程的某个事件

为什么要有等待

  • 在某些情况下,父子进程需要在执行过程中相互协同工作,以确保程序的正确性和完整性
  • 等待可以用于协调它们的活动,使它们按照某种特定的顺序执行任务
  • 父进程可能需要等待这些子进程完成执行,以便获取子进程的结果、收集信息或继续执行其他操作
  • 只有当父进程获取到子进程的退出状态信息时, 才会回收子进程资源(所以,如果父进程提前退出,将会造成子进程的资源泄漏)

内存泄漏

  • 这里的内存泄漏是指进程的task_struct还保留在内存中(也就是系统资源泄漏)
  • task_struct由os维护,其他人没有权限来释放,只能等待父进程回收 / 父进程退出后由init进程回收
  • 而我们平常说的内存泄漏是指进程在堆上开辟的空间没有被释放
  • 但其实一旦进程退出,这些由语言申请的空间都会被释放掉

如何等待

介绍

 头文件:

pid_t wait (int* status)

介绍
  • 用于父进程等待子进程的终止,并获取其退出状态
  • 如果当前没有子进程终止,父进程将被阻塞,等待一个子进程终止(也就是阻塞式等待)
  • 当一个子进程终止后,父进程会解除阻塞,并获取已终止子进程的pid(返回值),如果没有子进程终止,返回-1
status指针
  • 用于存储子进程的退出状态信息
  • 当该位置有参数时,由os提供该值
  • 如果不关心子进程的退出状态,可以传递NULL
示例

下面的代码流程是:

  • 创建一个子进程,子进程打印信息后睡眠5s,
  • 而父进程打印信息后睡眠7s,再等待进程状态变化
  • (其中的2s内可以看到子进程处于僵尸状态)
  • 后调用wait函数,用以回收已终止子进程的资源

可以看到,我们成功等待到了子进程:

 

子进程24295从僵尸状态Z+ -> 进程退出了,说明父进程等待子进程成功后,就释放掉了子进程的资源

pid_t waitpid (pid_t pid,int* status,int options)

pid
  • 如果为-1,则代表等待任意一个子进程终止(与wait()等价)
  • >0,它在等待指定的子进程
 options

默认是0,进行阻塞式等待

  • 此时父进程不做任何事,在等待队列中等待子进程的状态改变
  • 不占用cpu的时间片,这期间调度器调用其他可用进程
  • 阻塞的上层表现 -- 该进程无反应(因为cpu没有调度它)
WNOHANG -- 非阻塞等待
  • 宏定义(系统提供的大写标识位:一般都是宏定义,将没有特殊意义的数字定义成宏,使用时更加清晰)
  • 如果指定的子进程状态未改变,立即返回0 
  • 此时,父进程就可以执行waitpid后面的代码了(可以在等待的过程中完成一些小的工作模块)
  • 父进程在继续执行其他任务的同时,周期性地调用waitpid函数,以不断检查子进程的状态,从而及时获取子进程的退出状态
示例

可以看到,在父进程在等待子进程的过程中,依然在工作中

最终成功等待到子进程

 

status

和wait中的status用法一样

如果为waitpid(-1,NULL,0),则与wait(NULL)等价,看到的结果相同

查看status

如果我们尝试查看status呢?就可以使用waitpid(ret,&status,0):

我们让子进程睡眠5s后,带着退出码=2023退出,然后由父进程等待子进程退出,拿到退出码

 但是,结果似乎并不是我们想象中的那样(虽然子进程成功退出了,但是status的值很奇怪捏):

拿到的退出码怎么会是59136呢?

status问题

为什么上面的代码会出现值不一样的情况呢?

实际上status在函数内部并不是当做一个整数来对待,而是拆成32位bit位

其中低16位是我们需要了解的

正常退出时,恢复退出码
  • 如果程序执行完毕 (可以通过 宏(WIFEXITED) / 退出码 来判断是否正常退出)
  • 它会将实际的退出码放入[status的低16位]的后8位,然后将该数作为status的值
  • 所以我们得到的status会比设置的退出码(2023)大很多
位运算
  • 可以通过位运算的方式,将原本的退出码还原 (status>>8) & 0x0000 00ff
  • 右移8位是为了将退出状态移到该数的低8位,然后按位与拿到低8位
  • 也可以通过拿到该值 (WEIXTSTATUS)
进程崩溃,查看signal
引入
  • 如果进程崩溃提前终止了呢?这时的退出状态就没有意义了(会是0)
  • 其实想一想,运行的程序崩溃 本质上就是 os将这个进程杀死了
  • 那它是如何在茫茫进程中,单单选中了崩溃的进程把它杀死了呢?
  • 是通过程序发送信号通知os的
kill指令
  • 实际上,我们的kill指令就是通过os向进程发送信号,来对进程进行操作
  • 其中,信号是有编号的,1-31是普通信号,我们要学的也就是这31个信号
  • 然后,通过将 信号 放入 status的低7位 (第8位是core dump标志,在gdb调试崩溃程序中用到)
  • 我们可以通过按位与拿到低7位(& 0x0000 007f)
示例 -- 野指针
示例 -- kill -9

除了出现错误使程序崩溃,也可以是外力直接杀掉

  • 当子进程在死循环时,可以使用kill指令杀掉它(通过发送信号)

为什么一定要通过上面的这些函数拿到退出信息

  • 如果设置为全局变量呢?似乎可以拿到
  • 但其实,因为你要根据不同的判断条件来修改退出码,所以此时必定发生写时拷贝
  • 那么就会导致父子进程中的那个全局变量实际上有两个,父进程无法拿到子进程的变量,进程之间具有独立性

  • 除此之外,信号你又该如何拿到呢?

那么,这些问题wait / waitpid 是如何解决的呢?

  • 首先,那些退出信息也是数据,它就被存放在描述进程的pcb中
  • 还记得僵尸进程吗?
  • 为什么它有害,就是因为它的task_struct还保留在内存中,占用着资源
  • 必须要由父进程拿到子进程的退出码,才能释放它
  • 但是,进程之前不是有独立性吗?父进程是如何拿到子进程中的退出码的?
  • 实际上是由os完成的
  • os会自动回收子进程的资源,并将退出状态信息传递给父进程
  • 那么,wait和waitpid也是一样的道理,也是os完成实际的工作

  • wait和waitpid是系统调用,它是由os提供的接口
  • 因此由os维护的task_struct自然可以被自己的接口调用啦!!!
  • 所以,父进程是无法拿到子进程内部的信息的,但是os帮它拿到了

http://www.ppmy.cn/news/1197315.html

相关文章

[SSD综述1.6] SSD固态硬盘参数图文解析_选购固态硬盘就像买衣服?

依公知及经验整理,原创保护,禁止转载。 专栏 《SSD入门到精通系列》 <<<< 返回总目录 <<<< ​ 传统的 HDD 是“马达+磁头+磁盘”的机械结构,而 SSD 则是“闪存介质+主控”的纯半导体芯片存储结构,两者在数据存储介质和读写方式上有着本质区别,这…

linux傻瓜式安装Java环境及中间件

linux配置Java环境及中间件 1.傻瓜式安装Java1.下载2.追加3.刷新测试 2.傻瓜式安装docker1.docker卸载2.docker安装 3.Docker傻瓜式安装Redis1.傻瓜式安装安装并配置 4.Docker傻瓜式安装RabbitMQ5.Docker傻瓜式安装MySql1.拉取2.配置 6.傻瓜式安装Nacos1.官网下载nacos2.SQL文件…

量化、蒸馏、分解、剪枝

量化、蒸馏、分解和剪枝都是用于深度学习模型压缩和优化的算法。 量化是一种用于减少深度学习模型计算量和内存消耗的技术。在深度学习中&#xff0c;模型通常使用高精度的浮点数表示参数和激活值&#xff0c;但这种表示方式会占用大量的内存和计算资源。而量化技术通过降低参数…

dubbo没有找到生产者

1、没有找到生产者 com.alibaba.dubbo.rpc.RpcException: No provider available from registry 127.0.0.1:2181 for service .... , please check status of providers(disabled, not registered or in blacklist)2、 查看是不是 对应的providers 没有 注册上去 找到 zk 对应…

代码随想录训练营第60天 | 503.下一个更大元素II ● 42. 接雨水● 84.柱状图中的最大矩形

503.下一个更大元素II 题目链接&#xff1a;https://leetcode.com/problems/next-greater-element-ii/ 解法&#xff1a; 由于是循环数组&#xff0c;可以直接把两个数组拼接在一起&#xff0c;然后使用单调栈求下一个最大值。 写法上&#xff0c;可以巧妙一些&#xff0c…

Docker 安装ELK7.7.1

(注&#xff1a;在安装之前&#xff0c;本方法必须安装jdk1.8以上版本) (注&#xff1a;如果在虚拟机下用可以直接按方法走即可&#xff0c;如果是想进行备份后在别的机器上进行相关操作&#xff0c;必须把所有带有172.17.0.6、192.168.8.166:9200和端口号都改成你自己的方可使…

【Java-框架-Mybatis】(01) 使用Mybatis框架操作MySQL数据库,快速上手

前言 使用"Mybatis"框架操作"MySQL"数据库&#xff0c;快速上手&#xff1b; 实操一 【说明】 通过"IntelliJ IDEA"软件来创建"Maven"项目&#xff1b;通过"Mybatis"框架完成"MySQL"数据库操作&#xff1b; 【环…

Java面试题-Redis-第三天(缓存更新策略-由旁路缓存策略衍生出的一系列问题)

1. 问&#xff1a;了解缓存更新策略吗&#xff1f; 了解 先说旁路缓存策略 说了那个写策略 2. 问&#xff1a;然后问为什么要用那种&#xff1a; 答&#xff1a;降低不一致情况出现 3. 问&#xff1a;为什么会不一致&#xff1f; 答&#xff1a;请求1先将缓存删了&#xff0c;…