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请求、信号、锁的获取等),但该操作需要的资源暂时不可用时,进程会被操作系统挂起,进入“等待队列”或“阻塞状态”。…...
寒假1.25
题解 web:[极客大挑战 2019]Upload 打开环境 上传一个一句话木马试试 只能上传图片那就再上传一次,bp抓包修改type-content为image/jpeg试试 不行 看来是文件后缀被绕过了,上传一个.html然后抓包改类型试试 上传成功了,但是提示‘<&…...
28. 【.NET 8 实战--孢子记账--从单体到微服务】--简易报表--报表定时器与报表数据修正
这篇文章是《.NET 8 实战–孢子记账–从单体到微服务》系列专栏的《单体应用》专栏的最后一片和开发有关的文章。在这片文章中我们一起来实现一个数据统计的功能:报表数据汇总。这个功能为用户查看月度、年度、季度报表提供数据支持。 一、需求 数据统计方面&…...
C++/stack_queue
目录 1.stack 1.1stack的介绍 1.2stack的使用 练习题: 1.3stack的模拟实现 2.queue的介绍和使用 2.1queue的介绍 2.2queue的使用 2.3queue的模拟实现 3.priority_queue的介绍和使用 3.1priority_queue的介绍 3.2priority_queue的使用 欢迎 1.stack 1.1stack…...
【Java】微服务找不到问题记录can not find user-service
一、问题描述 运行网关微服务与用户微服务后,nacos服务成功注册 但是测试接口的时候网关没有找到相关服务 二、解决方案 我先检查了pom文件确定没问题后查看配置文件 最后发现是配置里spring.application.namexxx-user里面服务的名字后面多了一个空格 三、总结…...
QT:图像上绘制图形
需求描述 1、展示一张图像 2、在图像上可以使用数据绘制图像:矩形、不规则图形、线条 3、有按键可以选择 概要设计 规划布局如下 1、左边是Qlabel 用于展示图片 2、右边是三个按钮 具体实现 1、 首先设计 UI 界面,对控件进行布局 在 mainwindow.u…...
基于java线程池和EasyExcel实现数据异步导入
基于java线程池和EasyExcel实现数据异步导入 2.代码实现 2.1 controller层 PostMapping("import")public void importExcel(MultipartFile file) throws IOException {importService.importExcelAsync(file);}2.2 service层 Resource private SalariesListener sa…...
HPO3:提升模型性能的高效超参数优化工具
引言 在当今快速发展的数据科学和机器学习领域中,超参数优化(Hyperparameter Optimization, HPO)是构建高性能模型不可或缺的一环。为了简化这一复杂过程,恒通网络科技团队推出了HPO3模块——一个专为Python开发者设计的强大库&a…...
重回C语言之老兵重装上阵(十五)C语言错误处理
C语言错误处理 在C语言中,错误处理是非常重要的一部分。C语言没有像高级语言(例如Python、Java)那样内建的异常处理机制(如try-catch),但它提供了几种方法来捕捉和处理错误。正确的错误处理可以提高程序的稳…...
使用国内镜像加速器解决 Docker Hub 拉取镜像慢或被屏蔽的问题
一、问题背景 Docker Hub 是 Docker 默认的镜像仓库,但由于网络限制,国内用户直接拉取镜像可能面临以下问题: 下载速度极慢(尤其是大镜像)。连接超时或完全被屏蔽(部分网络环境)。依赖国外源的…...
为AI聊天工具添加一个知识系统 之76 详细设计之17 正则表达式 之4 正则表达式模板
Q712、三“化” (使用三种不同的定义方法:规定定义法 -线性回归/内涵定义法--一阶迭代/外延定义法--单调递归) 整体形成 一个双人零和 的局面 <Class()外延式, Type()内涵式> Method()规定式。给出 问题“law 是什么”的三种答案&#…...
日志收集Day007
1.配置ES集群TLS认证: (1)elk101节点生成证书文件 cd /usr/share/elasticsearch ./bin/elasticsearch-certutil cert -out config/elastic-certificates.p12 -pass "" --days 3650 (2)elk101节点为证书文件修改属主和属组 chown elasticsearch:elasticsearch con…...
【Python】 使用pygame库实现新年烟花
祝大家金蛇衔财,蛇来运转 首先,确保你已经安装了 pygame 库。如果还没有安装,可以通过以下命令安装: pip install pygame接下来是烟花效果的 Python 代码: import pygame import random import math import sys# 初始…...
C语言中string.h头文件功能介绍
在C语言的世界里,string.h头文件提供了许多用于处理字符串和内存操作的函数。今天,我们就来深入探讨string.h头文件的功能、使用注意事项以及一些拓展应用。 一、功能介绍 string.h头文件定义了一系列用于操作字符串和内存的函数。这些函数可以分为几个…...
群晖docker获取私有化镜像http: server gave HTTP response to HTTPS client].
群晖docker获取私有化镜像提示http: server gave HTTP response to HTTPS clien 问题描述 层级时间用户事件Information2023/07/08 12:47:45cxlogeAdd image from xx.xx.31.240:1923/go-gitea/gitea:1.19.3Error2023/07/08 12:47:48cxlogeFailed to pull image [Get "http…...
react antd点击table单元格文字下载指定的excel路径
在使用 Ant Design (antd) 的 Table 组件时,如果想点击表格单元格中的文字来触发下载指定路径的 Excel 文件,可以通过以下步骤实现: 1. 确保有一个可供下载的 Excel 文件:需要有一个服务器端点或者一个可以直接访问的 URL…...
《哈佛家训》
《哈佛家训》是2010年由威廉贝纳德撰写,张玉翻译,在中国妇女出版社出版的专著。书中有许多富有哲理的故事,传达了诸多教育理念和人生智慧,以下是一些例子及相应启示: ### 眼界与格局方面 - **故事**:小伙子…...
ResNeSt: Split-Attention Networks论文学习笔记
这张图展示了一个名为“Split-Attention”的神经网络结构,该结构在一个基数组(cardinal group)内进行操作。基数组通常指的是在神经网络中处理的一组特征或通道。图中展示了如何通过一系列操作来实现对输入特征的注意力机制。 以下是图中各部…...
【matlab】绘图 离散数据--->连续函数
matlab绘图练习 离散数据及离散函数对离散区间进行细划分 达到连续效果画plot(y)图 与 复数的应用 离散数据及离散函数 例1 x1[1 2 4 6 7 8 10 11 12 14 16 17 18 20] y1[1 2 4 6 7 8 10 10 8 7 6 4 2 1] figure(1); plot(x1,y1,o,MarkerSize,15); x21:20; y2log(x2); figure…...
pyside6-uic form.ui -o ui_form.py 的作用
pyside6-uic form.ui -o ui_form.py 的作用 pyside6-uic form.ui -o ui_form.py 这个命令是用来将 .ui 文件转换为 Python 代码文件的工具。 具体作用: pyside6-uic:这是一个命令行工具,用于将用 Qt Designer 或其他图形界面工具创建的 .ui …...
Qt中QVariant的使用
1.使用QVariant实现不同类型数据的相加 方法:通过type函数返回数值的类型,然后通过setValue来构造一个QVariant类型的返回值。 函数: QVariant mainPage::dataPlus(QVariant a, QVariant b) {QVariant ret;if ((a.type() QVariant::Int) &a…...
基础项目实战——3D赛车(c++)
目录 前言一、渲染引擎二、关闭事件三、梯形绘制四、轨道绘制五、边缘绘制六、草坪绘制七、前后移动八、左右移动九、曲线轨道十、课山坡轨道十一、循环轨道十二、背景展示十三、引入速度十四、物品绘制十五、课数字路障十六、分数展示十七、重新生成十八、…...
【SpringBoot教程】Spring Boot + MySQL + HikariCP 连接池整合教程
🙋大家好!我是毛毛张! 🌈个人首页: 神马都会亿点点的毛毛张 在前面一篇文章中毛毛张介绍了SpringBoot中数据源与数据库连接池相关概念,今天毛毛张要分享的是关于SpringBoot整合HicariCP连接池相关知识点以及底层源码…...
每日一题 430. 扁平化多级双向链表
430. 扁平化多级双向链表 简单 /*class Solution { public:Node* flatten(Node* head) {Node* tail nullptr;return dfs(head);}Node* dfs(Node* head){Node* cur head;while(cur ! nullptr){if(cur->child ! nullptr){Node* curChild getTail(cur->child);Node* te…...
vue3表格数据分2个表格序号连续展示
一、el-table表格在弹窗里面分两个表格展示。 假设我们有一个数组 tableData,我们希望在第一个表格中展示前半部分的数据,第二个表格中展示后半部分的数据。 <template><el-button type"primary" click"dialogVisible true&q…...
【愚公系列】《循序渐进Vue.js 3.x前端开发实践》027-组件的高级配置和嵌套
标题详情作者简介愚公搬代码头衔华为云特约编辑,华为云云享专家,华为开发者专家,华为产品云测专家,CSDN博客专家,CSDN商业化专家,阿里云专家博主,阿里云签约作者,腾讯云优秀博主,腾讯云内容共创官,掘金优秀博主,亚马逊技领云博主,51CTO博客专家等。近期荣誉2022年度…...
一文讲解Java中Object类常用的方法
在Java中,经常提到一个词“万物皆对象”,其中的“万物”指的是Java中的所有类,而这些类都是Object类的子类; Object主要提供了11个方法,大致可以分为六类: 对象比较: public native int has…...
操作系统之输入输出
🧑 博主简介:CSDN博客专家,历代文学网(PC端可以访问:https://literature.sinhy.com/#/literature?__c1000,移动端可微信小程序搜索“历代文学”)总架构师,15年工作经验,…...
【Convex Optimization Stanford】Lec3 Function
【Convex Optimization Stanford】Lec3 Function 前言凸函数的定义对凸函数在一条线上的限制增值扩充? 一阶条件二阶条件一些一阶/二阶条件的例子象集和sublevel set关于函数凸性的扩展(Jesen Inequality)保持函数凸性的操作非负加权和 & 仿射函数的…...
【Linux探索学习】第二十七弹——信号(一):Linux 信号基础详解
Linux学习笔记: https://blog.csdn.net/2301_80220607/category_12805278.html?spm1001.2014.3001.5482 前言: 前面我们已经将进程通信部分讲完了,现在我们来讲一个进程部分也非常重要的知识点——信号,信号也是进程间通信的一…...
