Linux进程调度与等待:背后的机制与实现
个人主页:chian-ocean
文章专栏-Linux
前言:
当一个进程发起某种操作(如I/O请求、信号、锁的获取等),但该操作需要的资源暂时不可用时,进程会被操作系统挂起,进入“等待队列”或“阻塞状态”。在此期间,进程不占用CPU,但仍保留其内存、文件描述符等资源

进程等待的必要性
僵尸进程的存在
僵尸进程的成因
- 当子进程终止后,它的退出状态需要由父进程通过调用
wait()或waitpid()系统调用回收。 - 如果父进程未回收子进程的退出状态,子进程会以“僵尸进程”的形式保留在进程表中。
特征:
- 在 Linux 系统中,可以用
ps命令查看,僵尸进程的状态为Z(Zombie)。 - 僵尸进程是操作系统保留的一个条目,主要用于父进程检查子进程的退出状态。
如下:

从图片中可以看到一个典型的 僵尸进程 的现象:
- 进程
27864被强制终止(kill -9 27864),但它的父进程(27863)没有调用wait()或waitpid()来回收其子进程的退出状态。 - 因此,
27864被标记为<defunct>状态,即僵尸进程。 ps输出的STAT列中显示Z+,这是僵尸进程的状态标识。
进程等待
进程等待是操作系统中一种重要的状态,指的是某个进程由于资源不足或条件未满足,暂时无法继续执行而被挂起的现象。
- 使用
wait()或waitpid()回收子进程
wait ( )
参数:
-
int *status:- 用于保存子进程的状态信息(如退出码或终止信号)。
- 如果不需要获取子进程状态,可以将其传入
NULL。
返回值:
- 成功:
- 返回已终止的子进程的 PID。
- 失败:
- 返回
-1,并设置errno。 - 常见错误包括:
ECHILD:当前进程没有子进程。EINTR:调用被信号中断。
- 返回
wait() 的作用
- 阻塞父进程:
wait()会阻塞父进程,直到任意一个子进程状态发生变化(通常是终止)。
- 回收子进程资源:
- 子进程终止后,其资源仍然保留在系统中,直到父进程调用
wait()或waitpid()回收它。 - 如果父进程不调用
wait()或waitpid(),子进程会变成 僵尸进程。
- 子进程终止后,其资源仍然保留在系统中,直到父进程调用
示例:
#include<iostream>
#include<unistd.h>
#include <sys/types.h>
#include <sys/wait.h>using namespace std;void childtast()
{for(int i = 0; i < 10; i++) // 循环打印从 0 到 9 的数字{cout << i << endl; // 输出当前的循环变量 i}sleep(3); // 睡眠 3 秒,模拟子进程的运行延迟
}int main()
{pid_t id = fork(); // 创建子进程cout << "id" << ":" << id << endl;if(id == 0) // 判断是否是子进程{sleep(3); // 子进程先睡眠 3 秒childtast(); // 子进程调用 childtast(),打印数字并睡眠}// 父进程等待任意一个子进程终止pid_t ret = wait(NULL); // 父进程调用 wait(),阻塞等待子进程终止if(ret == id) // 判断 wait() 返回的进程 ID 是否是创建的子进程 ID{cout << "ret" << ":" << ret << endl; // 输出子进程的 IDcout << "wait success" << endl; // 输出等待成功的消息}sleep(3); // 父进程再睡眠 3 秒,模拟延迟return 0;
}
fork() 创建子进程:
- 父进程和子进程同时运行。
- 父进程的
id是子进程的 PID,子进程的id是 0。
子进程的任务:
- 子进程先睡眠 3 秒,然后执行
childtast(),打印0到9。
父进程的等待:
- 父进程调用
wait(NULL),阻塞自身,直到子进程终止。 - 当子进程完成任务并退出后,
wait()返回子进程的 PID。
父进程的后续操作:
- 父进程输出子进程的
PID和等待成功的消息。 - 父进程再睡眠 3 秒后退出。
waitpid ( )
waitpid() 是 wait() 的增强版本,提供了更灵活的功能,允许父进程:
- 等待特定的子进程。
- 非阻塞等待子进程。
- 获取子进程的状态(如退出状态或被信号终止)。
pid_t waitpid(pid_t pid, int *status, int options);
参数说明
-
pid:-
pid > 0:等待特定的子进程(指定的 PID)。 -
pid == 0:等待与当前进程同一个进程组的任意子进程。 -
pid < -1:等待进程组 ID 为|pid|的任意子进程。wait(NULL) //等价于 waitpid(-1,NULL,0); -
pid == -1:等效于wait(),等待任意子进程。
-
status 字段的结构
status:
- 指向一个整数的指针,用于存储子进程的状态信息(退出状态、信号等)。
- 若不关心状态信息,可将其设为
NULL。
在 Linux 系统中,status 是一个整数,表示子进程状态的多种可能性,底层通过位字段表示:
| 位字段 | 含义 |
|---|---|
| 位 0-7 | 子进程退出的信号或退出码(低 8 位)。 |
| 位 8-15 | 退出状态(高 8 位,存储正常退出码)。 |
| 位 16-23 | 暂停信号编号。 |
代码解析字段
#include<iostream>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
using namespace std;
int main()
{ pid_t id = fork(); cout << "id" << ":" << id <<endl; if(id == 0) { sleep(3); exit(1); } int status; pid_t ret = waitpid(-1,&status,0); if(ret == id) {cout << "ret" << ":" << ret <<endl; cout<< "wait success" <<endl; } cout <<"status :" << status << endl;cout << "退出码" << ((status >> 8)& 0xff ) <<" "<< "信号码" << (status & 0x7f)<< endl;return 0;
}
完整运行流程
fork() 创建子进程:
- 父进程创建子进程,并返回子进程的 PID。
子进程逻辑:
- 子进程休眠 3 秒后正常退出,退出码为
1。
父进程逻辑:
- 父进程调用
waitpid()阻塞等待子进程终止。 - 获取子进程的状态信息,并解析退出码和信号码。
父进程输出状态信息:
- 输出子进程的 PID、状态值、退出码和信号码。
解析逻辑:
-
退出码:
(status >> 8) & 0xff- 获取高 8 位的退出码。
-
信号码:
status & 0x7f- 获取低 7 位的信号码.
示例1:进程正常退出的退出码。

示例2:提取被9号信号杀死的进程信号码

id:1667 // 父进程输出,子进程的 PID 是 1667 id:0 // 子进程输出,表明当前是子进程 ret:1667 // 父进程成功等待到子进程结束,返回子进程 PID wait success // 父进程确认子进程终止 status :9 // 父进程获取子进程状态值为 9 退出码0 信号码9 // 父进程解析状态值:// - 退出码 0:子进程未通过 exit() 返回退出码// - 信号码 9:子进程被 SIGKILL 信号终止库中提供的宏替换
解析退出码和信号编号
WIFEXITED(status):- 如果为真,表示子进程正常退出,其退出码存储在高 8 位。
- 使用
(status >> 8) & 0xff提取退出码。
WEXITSTATUS(status):- 获取退出码的宏,等价于
(status >> 8) & 0xff。 - 必须确保
WIFEXITED(status)为真后使用。
- 获取退出码的宏,等价于
解析退出码和信号编号
WIFEXITED(status)- 如果为真,表示子进程正常退出,其退出码存储在高 8 位。
- 使用
(status >> 8) & 0xff提取退出码。
WEXITSTATUS(status):==status & 0x7f- 获取退出码的宏,
- 必须确保
WIFEXITED(status)为真后使用。
options参数介绍
阻塞与非阻塞
| 特性 | 阻塞 | 非阻塞 |
|---|---|---|
| 进程状态 | 等待资源时挂起,无法执行其他任务。 | 立即返回,不会挂起,进程可执行其他任务。 |
| 适用场景 | 简单任务、对实时性要求不高的任务。 | 多任务并发、实时性要求高的任务。 |
| 复杂性 | 实现简单,逻辑清晰。 | 逻辑复杂,需要轮询或回调处理资源状态。 |
| CPU 使用 | 不浪费 CPU 资源,进程处于挂起状态。 | 需要轮询资源状态,可能增加 CPU 占用。 |
| 资源管理 | 等待资源的管理交由操作系统处理。 | 需要程序主动检查资源状态,增加开发复杂度。 |
options:
- 用于指定额外的选项:
0:阻塞等待。WNOHANG:非阻塞等待。WUNTRACED:返回暂停的子进程状态(子进程因SIGSTOP信号暂停)。WCONTINUED:返回恢复运行的子进程状态(子进程因SIGCONT信号继续运行)。
WNOHANG
- 非阻塞模式:
- 如果没有子进程终止,
waitpid()会立即返回,而不是阻塞父进程。
- 如果没有子进程终止,
- 返回值:
- 如果有子进程状态变化,则返回子进程的 PID。
- 如果没有子进程状态变化,则返回
0。
非阻塞轮询
#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <chrono>
#include <thread>
using namespace std;
int main() {pid_t pid = fork(); // 创建子进程if (pid == 0) {// 子进程逻辑cout << "Child process running..." << endl;sleep(5); // 模拟子进程任务,延迟 5 秒cout << "Child process exiting..." << endl;exit(42); // 子进程以退出码 42 正常退出} else if (pid > 0) {// 父进程逻辑int status;while (true) {pid_t ret = waitpid(-1, &status, WNOHANG); // 非阻塞检查子进程状态if (ret == 0) {// 子进程尚未终止,父进程继续其他工作cout << "Child process still running. Parent doing other work..." << endl;this_thread::sleep_for(chrono::seconds(1)); // 模拟父进程任务} else if (ret > 0) {// 子进程已终止,解析状态if (WIFEXITED(status)) {cout << "Child process " << ret << " exited with code " << WEXITSTATUS(status) << endl;} else if (WIFSIGNALED(status)) {cout << "Child process " << ret << " was terminated by signal " << WTERMSIG(status) << endl;}break; // 结束轮询} else {// waitpid 出错perror("waitpid failed");break;}}} else {// fork 失败perror("fork failed");return 1;}return 0;
}
执行结果:

多进程下的进程等待
阻塞等待多个子进程
示例代码:等待所有子进程完成
#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
using namespace std;int main() {// 创建多个子进程for (int i = 0; i < 3; ++i) {pid_t pid = fork();if (pid == 0) {// 子进程cout << "Child " << i << " (PID: " << getpid() << ") running..." << endl;sleep(2 + i); // 每个子进程休眠不同时间cout << "Child " << i << " (PID: " << getpid() << ") exiting..." << endl;exit(i); // 子进程以其序号为退出码}}// 父进程:等待所有子进程完成int status;while (true) {pid_t ret = wait(&status); // 阻塞等待任意一个子进程结束if (ret == -1) {// 没有子进程可等待时退出循环cout << "All child processes have finished." << endl;break;// 解析子进程状态if (WIFEXITED(status)) {cout << "Child process " << ret << " exited with code: " << WEXITSTATUS(status) << endl;} else if (WIFSIGNALED(status)) {cout << "Child process " << ret << " was terminated by signal: " << WTERMSIG(status) << endl;}}return 0;
}
代码执行:

非阻塞轮询等待多个子进程
示例代码:非阻塞等待多个子进程
通过 waitpid() 配合 WNOHANG 实现父进程的非阻塞轮询,定期检查是否有子进程完成。
#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <chrono>
#include <thread>
using namespace std;int main() {// 创建多个子进程for (int i = 0; i < 3; ++i) {pid_t pid = fork();if (pid == 0) {// 子进程cout << "Child " << i << " (PID: " << getpid() << ") running..." << endl;sleep(2 + i); // 每个子进程休眠不同时间cout << "Child " << i << " (PID: " << getpid() << ") exiting..." << endl;exit(i); // 子进程以其序号为退出码}}// 父进程:非阻塞轮询等待所有子进程完成int status;int completed = 0; // 已完成的子进程计数while (completed < 3) {pid_t ret = waitpid(-1, &status, WNOHANG); // 非阻塞检查子进程状态if (ret > 0) {// 有子进程状态变化completed++;if (WIFEXITED(status)) {cout << "Child process " << ret << " exited with code: " << WEXITSTATUS(status) << endl;} else if (WIFSIGNALED(status)) {cout << "Child process " << ret << " was terminated by signal: " << WTERMSIG(status) << endl;}} else if (ret == 0) {// 没有子进程状态变化,父进程继续其他工作cout << "No child process exited yet. Parent doing other work..." << endl;this_thread::sleep_for(chrono::seconds(1)); // 模拟其他任务} else {// 错误处理perror("waitpid failed");break;}}cout << "All child processes have finished." << endl;return 0;
}
代码执行:

相关文章:
Linux进程调度与等待:背后的机制与实现
个人主页:chian-ocean 文章专栏-Linux 前言: 当一个进程发起某种操作(如I/O请求、信号、锁的获取等),但该操作需要的资源暂时不可用时,进程会被操作系统挂起,进入“等待队列”或“阻塞状态”。…...
网易云音乐歌名可视化:词云生成与GitHub-Pages部署实践
引言 本文将基于前一篇爬取的网易云音乐数据, 利用Python的wordcloud、matplotlib等库, 对歌名数据进行深入的词云可视化分析. 我们将探索不同random_state对词云布局的影响, 并详细介绍如何将生成的词云图部署到GitHub Pages, 实现数据可视化的在线展示. 介绍了如何从原始数据…...
单片机基础模块学习——DS18B20温度传感器芯片
不知道该往哪走的时候,就往前走。 一、DS18B20芯片原理图 该芯片共有三个引脚,分别为 GND——接地引脚DQ——数据通信引脚VDD——正电源 数据通信用到的是1-Wier协议 优点:占用端口少,电路设计方便 同时该协议要求通过上拉电阻…...
《网络数据安全管理条例》施行,企业如何推进未成年人个人信息保护(下)
文章目录 前言三、全流程推进未成年人个人信息保护1、处理前:未成年人个人信息处理的告知同意2、处理中:加强个人信息处理流程管理3、处理后:落实个人信息保护合规审计四、大型网络平台应每年发布社会责任报告前言 《网数条例》颁布前,我国已针对未成年人个人信息保护陆续…...
书生大模型实战营3
文章目录 L0——入门岛git基础Git 是什么?Git 中的一些基本概念工作区、暂存区和 Git 仓库区文件状态分支主要功能 Git 平台介绍GitHubGitLabGitee Git 下载配置验证下载 Git配置 Git验证 Git配置 Git常用操作Git简易入门四部曲Git其他指令 闯关任务任务1: 破冰活动…...
Spring Boot 集成 WebClient 实战教程 实现同步、异步请求处理以及响应式编程、响应式流、响应式Mono
该项目介绍springboot集成WebClient 实现服务的请求操作 示例中演示了,如何配置WebClient的请求头,请求参数等相关参数,实现同步、异步请求处理以及响应式编程、响应式流、响应式Mono。 为什么使用WebClient 不用RestTemplate 在 Spring Framework 5.0 及更高版本中,Res…...
C语言深入解析 printf的底层源码实现
深入解析 printf 的底层源码实现 printf 是 C 标准库中最常用的函数之一,用于格式化输出字符串。它的底层实现复杂且高效,包含多个模块化的函数和机制。本文结合 GNU C Library(glibc)的源码,详细分析 printf 的实现原…...
go 循环处理无限极数据
数据表结构: CREATE TABLE permission (id int(11) NOT NULL AUTO_INCREMENT COMMENT 权限ID,permission_name varchar(255) DEFAULT NULL COMMENT 权限名称,permission_url varchar(255) DEFAULT NULL COMMENT 权限路由,status tinyint(1) DEFAULT NULL COMMENT 权…...
C# Dynamic关键字
一、引言:开启动态编程之门 在 C# 的编程世界里,长久以来我们习惯了静态类型语言带来的严谨与稳定。在传统的 C# 编程中,变量的类型在编译时就已经确定,这就像是给每个变量贴上了一个固定的标签,在整个代码执行过程中…...
ReactNative react-devtools 夜神模拟器连调
目录 一、安装react-devtools 二、在package.json中配置启动项 三、联动 一、安装react-devtools yarn add react-devtools5.3.1 -D 这里选择5.3.1版本,因为高版本可能与夜神模拟器无法联动,导致部分功能无法正常使用。 二、在package.json中配置启…...
【教学类-89-02】20250128新年篇02——姓名藏头对联(星火讯飞+Python,五言对联,有横批)
背景需求: 过年了,我想用幼儿的名字写对联,但是我根本不会写,于是尝试让AI来写。 1.我班的孩子的名字都是2字和3字的 2.惊喜发现,AI它很快就能生成带名字的对联 但是观察发现,如果是二个名字的对联&#…...
装机爱好者的纯净工具箱
对于每一位电脑用户来说,新电脑到手后的第一件事通常是检测硬件性能。今天为大家介绍一款开源且无广告的硬件检测工具——入梦工具箱。 主要功能 硬件信息一目了然 打开入梦工具箱,首先看到的是硬件信息概览。这里不仅包含了内存、主板、显卡、硬盘等常…...
【新春不断更】数据结构与算法之美:二叉树
Hello大家好,我是但凡!很高兴我们又见面啦! 眨眼间已经到了2024年的最后一天,在这里我要首先感谢过去一年陪我奋斗的每一位伙伴,是你们给予我不断前行的动力。银蛇携福至,万象启新程。蛇年新春之际…...
网站结构优化:加速搜索引擎收录的关键
本文来自:百万收录网 原文链接:https://www.baiwanshoulu.com/9.html 网站结构优化对于加速搜索引擎收录至关重要。以下是一些关键策略,旨在通过优化网站结构来提高搜索引擎的抓取效率和收录速度: 一、合理规划网站架构 采用扁…...
Effective Objective-C 2.0 读书笔记—— objc_msgSend
Effective Objective-C 2.0 读书笔记—— objc_msgSend 文章目录 Effective Objective-C 2.0 读书笔记—— objc_msgSend引入——静态绑定和动态绑定OC之中动态绑定的实现方法签名方法列表 其他方法objc_msgSend_stretobjc_msgSend_fpretobjc_msgSendSuper 尾调用优化总结参考文…...
[MySQL]事务的隔离级别原理与底层实现
目录 1.为什么要有隔离性 2.事务的隔离级别 读未提交 读提交 可重复读 串行化 3.演示事务隔离级别的操作 查看与设置事务的隔离级别 演示读提交操作 演示可重复读操作 1.为什么要有隔离性 在真正的业务场景下,MySQL服务在同一时间一定会有大量的客户端进程…...
项目升级Sass版本或升级Element Plus版本遇到的问题
项目升级Sass版本或升级Element Plus版本遇到的问题 如果项目有需求需要用到高版本的Element Plus组件,则需要升级相对应的sass版本,Element 文档中有提示,2.8.5及以后得版本,sass最低支持的版本为1.79.0,所升级sass、…...
C++中,存储两个相同类型的数据,数据结构
在C中,存储两个相同类型的数据,可以使用多种数据结构。这里有几种常见且合适的选择: 简单的变量: 最直接的方式就是使用两个独立的变量。这种方法简单直观,但不够结构化。 cpp int a 5; int b 10; std::pair&#x…...
python实战(十五)——中文手写体数字图像CNN分类
一、任务背景 本次python实战,我们使用来自Kaggle的数据集《Chinese MNIST》进行CNN分类建模,不同于经典的MNIST数据集,我们这次使用的数据集是汉字手写体数字。除了常规的汉字“零”到“九”之外还多了“十”、“百”、“千”、“万”、“亿…...
[论文阅读] (37)CCS21 DeepAID:基于深度学习的异常检测(解释)
祝大家新春快乐,蛇年吉祥! 《娜璋带你读论文》系列主要是督促自己阅读优秀论文及听取学术讲座,并分享给大家,希望您喜欢。由于作者的英文水平和学术能力不高,需要不断提升,所以还请大家批评指正࿰…...
深入浅出Asp.Net Core MVC应用开发系列-AspNetCore中的日志记录
ASP.NET Core 是一个跨平台的开源框架,用于在 Windows、macOS 或 Linux 上生成基于云的新式 Web 应用。 ASP.NET Core 中的日志记录 .NET 通过 ILogger API 支持高性能结构化日志记录,以帮助监视应用程序行为和诊断问题。 可以通过配置不同的记录提供程…...
docker详细操作--未完待续
docker介绍 docker官网: Docker:加速容器应用程序开发 harbor官网:Harbor - Harbor 中文 使用docker加速器: Docker镜像极速下载服务 - 毫秒镜像 是什么 Docker 是一种开源的容器化平台,用于将应用程序及其依赖项(如库、运行时环…...
反向工程与模型迁移:打造未来商品详情API的可持续创新体系
在电商行业蓬勃发展的当下,商品详情API作为连接电商平台与开发者、商家及用户的关键纽带,其重要性日益凸显。传统商品详情API主要聚焦于商品基本信息(如名称、价格、库存等)的获取与展示,已难以满足市场对个性化、智能…...
day52 ResNet18 CBAM
在深度学习的旅程中,我们不断探索如何提升模型的性能。今天,我将分享我在 ResNet18 模型中插入 CBAM(Convolutional Block Attention Module)模块,并采用分阶段微调策略的实践过程。通过这个过程,我不仅提升…...
2025盘古石杯决赛【手机取证】
前言 第三届盘古石杯国际电子数据取证大赛决赛 最后一题没有解出来,实在找不到,希望有大佬教一下我。 还有就会议时间,我感觉不是图片时间,因为在电脑看到是其他时间用老会议系统开的会。 手机取证 1、分析鸿蒙手机检材&#x…...
Spring Boot+Neo4j知识图谱实战:3步搭建智能关系网络!
一、引言 在数据驱动的背景下,知识图谱凭借其高效的信息组织能力,正逐步成为各行业应用的关键技术。本文聚焦 Spring Boot与Neo4j图数据库的技术结合,探讨知识图谱开发的实现细节,帮助读者掌握该技术栈在实际项目中的落地方法。 …...
【OSG学习笔记】Day 16: 骨骼动画与蒙皮(osgAnimation)
骨骼动画基础 骨骼动画是 3D 计算机图形中常用的技术,它通过以下两个主要组件实现角色动画。 骨骼系统 (Skeleton):由层级结构的骨头组成,类似于人体骨骼蒙皮 (Mesh Skinning):将模型网格顶点绑定到骨骼上,使骨骼移动…...
Android15默认授权浮窗权限
我们经常有那种需求,客户需要定制的apk集成在ROM中,并且默认授予其【显示在其他应用的上层】权限,也就是我们常说的浮窗权限,那么我们就可以通过以下方法在wms、ams等系统服务的systemReady()方法中调用即可实现预置应用默认授权浮…...
A2A JS SDK 完整教程:快速入门指南
目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库ÿ…...
day36-多路IO复用
一、基本概念 (服务器多客户端模型) 定义:单线程或单进程同时监测若干个文件描述符是否可以执行IO操作的能力 作用:应用程序通常需要处理来自多条事件流中的事件,比如我现在用的电脑,需要同时处理键盘鼠标…...
