Linux进程控制——Linux进程等待
前言:接着前面进程终止,话不多说我们进入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){// childWorker();exit(0);}else{sleep(10);// fatherpid_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){// childWorker();exit(0);}else{// fatherprintf("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方法
waitpid和wait都是等待进程。waitpid可以指定等待一个进程,且有三个参数


4. 获取子进程status
父进程想要知道子进程的退出信息,也就是退出码和退出信号,就要用到输出型参数status
- wait和waitpid,都有一个status参数,该参数是一个输出型参数,由操作系统填充。
- 如果传递NULL,表示不关心子进程的退出状态信息。
- 否则,操作系统会根据该参数,将子进程的退出信息反馈给父进程。
int main(){ pid_t id = fork();if(id == 0){// childWorker();exit(10); // 设置成10方便观察现象}else{// fatherprintf("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){// childWorker();exit(1);}else{// sleep(10);// fatherprintf("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
这样父进程就获取到了子进程的退出信息
拓展二:我们为什么不用全局变量获取子进程的退出信息而用系统调用?
这个就是因为进程具有独立性,父进程无法直接获得子进程的退出信息
总结:
进程等待确实非常有用,它既可以回收僵尸进程,避免造成内存泄漏,也能让父进程能够获取到子进程的退出信息,进程等待我们就先了解这么多,进程控制马上就到了我们的最后一步——进程替换,让我们来期待下一篇!
谢谢大家支持本篇到这里就结束了

相关文章:
Linux进程控制——Linux进程等待
前言:接着前面进程终止,话不多说我们进入Linux进程等待的学习,如果你还不了解进程终止建议先了解: Linux进程终止 本篇主要内容: 什么是进程等待 为什么要进行进程等待 如何进程等待 进程等待 1. 进程等待的概念2. 进…...
GPT-4o:融合文本、音频和图像的全方位人机交互体验
引言: GPT-4o(“o”代表“omni”)的问世标志着人机交互领域的一次重要突破。它不仅接受文本、音频和图像的任意组合作为输入,还能生成文本、音频和图像输出的任意组合。这一全新的模型不仅在响应速度上达到了惊人的水平,在文本、音频和图像理解方面也表现出色,给人带来了…...
灵活的静态存储控制器 (FSMC)的介绍(STM32F4)
目录 概述 1 认识FSMC 1.1 应用介绍 1.2 FSMC的主要功能 1.2.1 FSMC用途 1.2.2 FSMC的功能 2 FSMC的框架结构 2.1 AHB 接口 2.1.1 AHB 接口的Fault 2.1.2 支持的存储器和事务 2.2 外部器件地址映射 3 地址映射 3.1 NOR/PSRAM地址映射 3.2 NAND/PC卡地址映射 概述…...
nginx-rtmp
1.已经安装nginx;configure配置模块;make编译无需安装;把objs/nginx复制到已安装的宁目录下 ./configure --prefix/usr/local/nginx --add-module/usr/local/src/fastdfs-nginx-module/src --add-module/usr/local/src/nginx-rtmp-module-mas…...
nginx 代理java 请求报502
情况:nginx代理java 请求 后端返回正常,但是经过nginx 时报502 经过多次对比其他接口发现可能是返回的请求头过大,导致nginx 报错:如下 2024/05/13 02:57:12 [error] 88#88: *3755 upstream sent too big header while reading r…...
面试集中营—Redis面试题
一、Redis的线程模型 Redis是基于非阻塞的IO复用模型,内部使用文件事件处理器(file event handler),这个文件事件处理器是单线程的,所以Redis才叫做单线程的模型,它采用IO多路复用机制同时监听多个socket&a…...
关于使用git拉取gitlab仓库的步骤(解决公钥问题和pytho版本和repo版本不对应的问题)
先获取权限,提交ssh-key 虚拟机连接 GitLab并提交代码_gitlab提交mr-CSDN博客 配置完成上诉步骤之后,执行下列指令进行拉去仓库的内容 sudo apt install repo export PATHpwd/.repo/repo:$PATH python3 "实际路径"/repo init -u ssh://gitxx…...
Django图书馆综合项目-学习(2)
接下来我们来实现一下图书管理系统的一些相关功能 1.在书籍的book_index.html中有一个"查看所有书毂"的超链接按钮,点击进入书籍列表book_list.html页面. 这边我们使用之前创建的命名空间去创建超连接 这里的book 是在根路由创建的namespacelist是在bo…...
vue3+ts 获取input 输入框中的值
从前端input 输入框获取值,通过封装axios 将值传给后端服务 数据格式为json html <el-form> <el-form-item label"域名"><el-input v-model"short_url" style"width: 240px"type"text"placeholder&quo…...
Gin框架返回Protobuf类型:提升性能的利器
在构建高效、高性能的微服务架构时,数据序列化和反序列化的性能至关重要。Protocol Buffers(简称Protobuf)作为一种轻量级且高效的结构化数据存储格式,已经在众多领域得到广泛应用。Gin框架作为Go语言中流行的Web框架,…...
HTML满屏漂浮爱心
目录 写在前面 满屏爱心 代码分析 系列推荐 写在最后 写在前面 小编给大家准备了满屏漂浮爱心代码,一起来看看吧~ 满屏爱心 文件heart.svg <svg xmlns"http://www.w3.org/2000/svg" width"473.8px" height"408.6px" view…...
爬虫应该选择住宅ip代理还是数据中心代理?
住宅代理 住宅代理是互联网服务提供商 (ISP) 提供的 IP 地址,它们是附加到实际物理位置的真实IP地址。住宅代理允许用户通过目标区域内的真实IP地址连接到互联网。 数据中心代理 数据中心代理是指是使用数据中心拥有并管理IP的代理,IP地址来源于数据中…...
百面算法工程师目录 | 深度学习目标检测、语义分割、分类上百种面试问答技巧
本文给大家带来的百面算法工程师是深度学习面试目录大纲,文章内总结了常见的提问问题,旨在为广大学子模拟出更贴合实际的面试问答场景。在这篇文章中,可以点击题目直达问题答案处,方便查找问题寻找答案。节约大家的时间。通过对这…...
Java中Maven的依赖管理
依赖介绍 是指当前项目运行所需要的jar包,一个项目中可以引入多个依赖 配置 在pom.xml中编写<dependencies>标签 在<dependencies>中使用<dependency>引入标签 定义坐标的groupId、rtifactId、version 点击刷新按钮、引入新坐标 例如引入下…...
Github新手入门使用方法
**存在问题:**新手如何快速入门github,能够下载开源文件,并且修改后更新远程github仓库; 解决方案: 参考: http://www.360doc.com/content/24/0301/12/60419_1115656653.shtml https://blog.csdn.net/gongd…...
期权隐含波动率到底是什么意思?
今天期权懂带你了解期权隐含波动率到底是什么意思?期权隐含波动率解析。通俗的说,期权隐含波动率是在期权市场中买家和卖家对于,某一期权合约价格变动幅度大小的判断。 期权隐含波动率到底是什么意思? 隐含波动率是根据期权市场价…...
28、Flink 为管理状态自定义序列化
为管理状态自定义序列化 a)概述 对状态使用自定义序列化,包含如何提供自定义状态序列化程序、实现允许状态模式演变的序列化程序。 b)使用自定义状态序列化程序 注册托管 operator 或 keyed 状态时,需要 StateDescriptor 来指…...
【强训笔记】day17
NO.1 思路:用一个字符串实现,stoi函数可以转化为数字并且去除前导0。 代码实现: #include <iostream> #include<string> using namespace std;string s;int main() {cin>>s;for(int i0;i<s.size();i){if(s[i]%20) s[…...
平滑 3d 坐标
3d平滑 import torch import torch.nn.functional as F import numpy as np import matplotlib.pyplot as plt from mpl_toolkits.mplot3d import Axes3Dclass SmoothOperator:def smooth(self, vertices):# 使用一维平均池化进行平滑vertices_smooth F.avg_pool1d(vertices.p…...
Go解析的数据类型可能含有不同数据结构的处理方式
最近做一个需求,各种业务消息都会往我的消息队列中写各种类型的数据,服务端需要接受各种不同的参数然后转换为本地数据结构,Go语言不确定上游传过来的数值是什么类型,然后又下面四种解决方案。 1. 类型断言和类型切换 func (Mis…...
华为云AI开发平台ModelArts
华为云ModelArts:重塑AI开发流程的“智能引擎”与“创新加速器”! 在人工智能浪潮席卷全球的2025年,企业拥抱AI的意愿空前高涨,但技术门槛高、流程复杂、资源投入巨大的现实,却让许多创新构想止步于实验室。数据科学家…...
在鸿蒙HarmonyOS 5中实现抖音风格的点赞功能
下面我将详细介绍如何使用HarmonyOS SDK在HarmonyOS 5中实现类似抖音的点赞功能,包括动画效果、数据同步和交互优化。 1. 基础点赞功能实现 1.1 创建数据模型 // VideoModel.ets export class VideoModel {id: string "";title: string ""…...
从零实现富文本编辑器#5-编辑器选区模型的状态结构表达
先前我们总结了浏览器选区模型的交互策略,并且实现了基本的选区操作,还调研了自绘选区的实现。那么相对的,我们还需要设计编辑器的选区表达,也可以称为模型选区。编辑器中应用变更时的操作范围,就是以模型选区为基准来…...
通过Wrangler CLI在worker中创建数据库和表
官方使用文档:Getting started Cloudflare D1 docs 创建数据库 在命令行中执行完成之后,会在本地和远程创建数据库: npx wranglerlatest d1 create prod-d1-tutorial 在cf中就可以看到数据库: 现在,您的Cloudfla…...
MMaDA: Multimodal Large Diffusion Language Models
CODE : https://github.com/Gen-Verse/MMaDA Abstract 我们介绍了一种新型的多模态扩散基础模型MMaDA,它被设计用于在文本推理、多模态理解和文本到图像生成等不同领域实现卓越的性能。该方法的特点是三个关键创新:(i) MMaDA采用统一的扩散架构…...
【git】把本地更改提交远程新分支feature_g
创建并切换新分支 git checkout -b feature_g 添加并提交更改 git add . git commit -m “实现图片上传功能” 推送到远程 git push -u origin feature_g...
爬虫基础学习day2
# 爬虫设计领域 工商:企查查、天眼查短视频:抖音、快手、西瓜 ---> 飞瓜电商:京东、淘宝、聚美优品、亚马逊 ---> 分析店铺经营决策标题、排名航空:抓取所有航空公司价格 ---> 去哪儿自媒体:采集自媒体数据进…...
MySQL账号权限管理指南:安全创建账户与精细授权技巧
在MySQL数据库管理中,合理创建用户账号并分配精确权限是保障数据安全的核心环节。直接使用root账号进行所有操作不仅危险且难以审计操作行为。今天我们来全面解析MySQL账号创建与权限分配的专业方法。 一、为何需要创建独立账号? 最小权限原则…...
Go 语言并发编程基础:无缓冲与有缓冲通道
在上一章节中,我们了解了 Channel 的基本用法。本章将重点分析 Go 中通道的两种类型 —— 无缓冲通道与有缓冲通道,它们在并发编程中各具特点和应用场景。 一、通道的基本分类 类型定义形式特点无缓冲通道make(chan T)发送和接收都必须准备好࿰…...
并发编程 - go版
1.并发编程基础概念 进程和线程 A. 进程是程序在操作系统中的一次执行过程,系统进行资源分配和调度的一个独立单位。B. 线程是进程的一个执行实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。C.一个进程可以创建和撤销多个线程;同一个进程中…...


