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() 的作用

  1. 阻塞父进程:
    • wait() 会阻塞父进程,直到任意一个子进程状态发生变化(通常是终止)。
  2. 回收子进程资源:
    • 子进程终止后,其资源仍然保留在系统中,直到父进程调用 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; // 输出子进程的 ID
        cout << "wait success" << endl; // 输出等待成功的消息
    }

    sleep(3); // 父进程再睡眠 3 秒,模拟延迟
    return 0;
}

fork() 创建子进程

  • 父进程和子进程同时运行。
  • 父进程的 id 是子进程的 PID,子进程的 id 是 0。

子进程的任务

  • 子进程先睡眠 3 秒,然后执行 childtast(),打印 09

父进程的等待

  • 父进程调用 wait(NULL),阻塞自身,直到子进程终止。
  • 当子进程完成任务并退出后,wait() 返回子进程的 PID。

父进程的后续操作

  • 父进程输出子进程的PID和等待成功的消息。
  • 父进程再睡眠 3 秒后退出。

waitpid ( )

waitpid()wait() 的增强版本,提供了更灵活的功能,允许父进程:

  1. 等待特定的子进程。
  2. 非阻塞等待子进程。
  3. 获取子进程的状态(如退出状态或被信号终止)。
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、状态值、退出码和信号码。

解析逻辑

解析退出码和信号编号
  • 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;
}

代码执行:

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

低代码系统-产品架构案例介绍、明道云(十一)

明道云HAP-超级应用平台(Hyper Application Platform)&#xff0c;其实就是企业级应用平台&#xff0c;跟微搭类似。 通过自设计底层架构&#xff0c;兼容各种平台&#xff0c;使用低代码做到应用搭建、应用运维。 企业级应用平台最大的特点就是隐藏在冰山下的功能很深&#xf…

DeepSeek大模型技术解析:从架构到应用的全面探索

一、引言 在人工智能领域&#xff0c;大模型的发展日新月异&#xff0c;其中DeepSeek大模型凭借其卓越的性能和广泛的应用场景&#xff0c;迅速成为业界的焦点。本文旨在深入剖析DeepSeek大模型的技术细节&#xff0c;从架构到应用进行全面探索&#xff0c;以期为读者提供一个…

python学opencv|读取图像(四十八)使用cv2.bitwise_xor()函数实现图像按位异或运算

【0】基础定义 按位与运算&#xff1a;两个等长度二进制数上下对齐&#xff0c;全1取1&#xff0c;其余取0。 按位或运算&#xff1a;两个等长度二进制数上下对齐&#xff0c;有1取1&#xff0c;其余取0。 按位取反运算&#xff1a;一个二进制数&#xff0c;0变1,1变0。 按…

DeepSeek辅助学术写作摘要内容

学术摘要写作 摘要是文章的精华&#xff0c;通常在200-250词左右。要包括研究的目的、方法、结果和结论。让AI工具作为某领域内资深的研究专家&#xff0c;编写摘要需要言简意赅&#xff0c;直接概括论文的核心&#xff0c;为读者提供快速了解的窗口。 下面我们使用DeepSeek编…

World Creator地形导入UE

修改导出分辨率1009x1009, 虚幻默认参数的整体分辨率是1009 导出预设选择高度图&#xff08;heigh map&#xff09;格式选择PNG 16位,或者RAW 16位&#xff0c;需要反转y轴&#xff08;与虚幻不同&#xff09;&#xff0c;命名格式会自动带一个 , 将改成_ 或者删掉自己命名 &am…

大数据Hadoop入门3

目录 第五部分&#xff08;Apache Hive DML语句和函数使用&#xff09; 1.课程内容大纲和学习目标 2.Hive SQL-DML-load加载数据操作 3.Hive SQL-DML-insert插入数据 4.Hive SQL-DML-select查询-语法书和环境准备 5.Hive SQL-DML-select查询-列表达式和distinct去重 6.Hi…

WPS数据分析000005

目录 一、数据录入技巧 二、一维表 三、填充柄 向下自动填充 自动填充选项 日期填充 星期自定义 自定义序列 1-10000序列 四、智能填充 五、数据有效性 出错警告 输入信息 下拉列表 六、记录单 七、导入数据 ​编辑 八、查找录入 会员功能 Xlookup函数 VL…

【Redis】hash 类型的介绍和常用命令

1. 介绍 Redis 中存储的 key-value 本身就是哈希表的结构&#xff0c;存储的 value 也可以是一个哈希表的结构 这里每一个 key 对应的一个 哈希类型用 field-value 来表示 2. 常用命令 命令 介绍 时间复杂度 hset key field value 用于设置哈希表 key 中字段 field 的值为…

基于STM32的阿里云智能农业大棚

目录 前言&#xff1a; 项目效果演示&#xff1a; 一、简介 二、硬件需求准备 三、硬件框图 四、CubeMX配置 4.1、按键、蜂鸣器GPIO口配置 4.2、ADC输入配置 4.3、IIC——驱动OLED 4.4、DHT11温湿度读取 4.5、PWM配置——光照灯、水泵、风扇 4.6、串口——esp8266模…

Ollama+DeepSeek本地大模型部署

1、Ollama 官网&#xff1a;https://ollama.com/ Ollama可以干什么&#xff1f; 可以快速在本地部署和管理各种大语言模型&#xff0c;操作命令和dokcer类似。 mac安装ollama&#xff1a; # 安装ollama brew install ollama# 启动ollama服务&#xff08;默认11434端口&#xf…

2025美赛MCM数学建模A题:《石头台阶的“记忆”:如何用数学揭开历史的足迹》(全网最全思路+模型)

✨个人主页欢迎您的访问 ✨期待您的三连 ✨ 《石头台阶的“记忆”&#xff1a;如何用数学揭开历史的足迹》 目录 《石头台阶的“记忆”&#xff1a;如何用数学揭开历史的足迹》 ✨摘要✨ ✨引言✨ 1. 引言的结构 2. 撰写步骤 &#xff08;1&#xff09;研究背景 &#…

定西市建筑房屋轮廓数据shp格式gis无偏移坐标(字段有高度和楼层)内容测评

定西市建筑房屋轮廓数据是GIS&#xff08;Geographic Information System&#xff0c;地理信息系统&#xff09;领域的重要资源&#xff0c;用于城市规划、土地管理、环境保护等多个方面。这份2022年的数据集采用shp&#xff08;Shapefile&#xff09;格式&#xff0c;这是一种…

fpga系列 HDL:XILINX Vivado Vitis 高层次综合(HLS) 实现 EBAZ板LED控制(上)

目录 创建工程创建源文件并编写C代码C仿真综合仿真导出RTL CG导出RTL错误处理&#xff1a; 创建工程 创建源文件并编写C代码 创建源文件(Souces下的hlsv.h和hlsv.cpp&#xff0c;Test Bench下的test_hlsv1.cpp)&#xff1a; hlsv1.h #ifndef HLSV1 #define HLSV1 #include &l…

gesp(C++六级)(4)洛谷:B3874:[GESP202309 六级] 小杨的握手问题

gesp(C六级)&#xff08;4&#xff09;洛谷&#xff1a;B3874&#xff1a;[GESP202309 六级] 小杨的握手问题 题目描述 小杨的班级里共有 N N N 名同学&#xff0c;学号从 0 0 0 至 N − 1 N-1 N−1。 某节课上&#xff0c;老师安排全班同学进行一次握手游戏&#xff0c;具…

DeepSeek-R1本地部署笔记

文章目录 效果概要下载 ollama终端下载模型【可选】浏览器插件 UIQ: 内存占用高&#xff0c;显存占用不高&#xff0c;正常吗 效果 我的配置如下 E5 2666 V3 AMD 590Gme 可以说是慢的一批了&#xff0c;内存和显卡都太垃圾了&#xff0c;回去用我的新设备再试试 概要 安装…

ESP32 I2S音频总线学习笔记(二):I2S读取INMP441音频数据

简介 在这个系列的上一篇文章中&#xff0c;我们介绍了ESP32 I2S音频总线的相关知识&#xff0c;简要了解了什么是I2S总线、它的通信格式&#xff0c;以及相关的底层API函数。没有看过上篇文章的可以点击文章进行回顾&#xff1a; ESP32 I2S音频总线学习笔记&#xff08;一&a…

CNN-GRU卷积门控循环单元时间序列预测(Matlab完整源码和数据)

CNN-GRU卷积门控循环单元时间序列预测&#xff08;Matlab完整源码和数据&#xff09; 目录 CNN-GRU卷积门控循环单元时间序列预测&#xff08;Matlab完整源码和数据&#xff09;预测效果基本介绍CNN-GRU卷积门控循环单元时间序列预测一、引言1.1、研究背景与意义1.2、研究现状1…

Springboot集成Swagger和Springdoc详解

Springboot2.x集成Swagger21. Springboot匹配版本2.7.0~2.7.18(其它版本需要自己去调试匹配)2. 首先导入Swagger2匹配的依赖项3. 导入依赖后创建配置文件SwaggerConfig4. Swagger集成完后,接下来接口的配置Springboot3.x集成Springdoc1. Springboot3.x依赖Springdoc配置2. 在…

【2024年华为OD机试】 (C卷,200分)- 矩阵匹配(JavaScriptJava PythonC/C++)

一、问题描述 问题描述 给定一个大小为 ( N \times M )(( N \leq M ))的矩阵,从中选出 ( N ) 个数,要求任意两个数字不能在同一行或同一列。求选出来的 ( N ) 个数中第 ( K ) 大的数字的最小值。 输入描述 输入矩阵要求:( 1 \leq K \leq N \leq M \leq 150 )输入格式:…

【python 逆向分析惠农网】分析惠农网的【headers】载荷签名,并实现获取公开数据,全程异步跟栈,仅供学习参考

文章日期&#xff1a;2025.1.24 使用工具&#xff1a;Python、Node.js 本章知识&#xff1a;分析惠农网的【headers】载荷签名&#xff0c;有点多&#xff0c;就不展示了 文章难度&#xff1a;中等&#xff08;比较麻烦&#xff0c;异步较多&#xff09; 文章全程已做去敏处理&…