Linux进程调度与等待:背后的机制与实现

news2025/4/20 15:51:25

个人主页: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;
}

代码执行:

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

寒假1.25

题解 web:[极客大挑战 2019]Upload 打开环境 上传一个一句话木马试试 只能上传图片那就再上传一次&#xff0c;bp抓包修改type-content为image/jpeg试试 不行 看来是文件后缀被绕过了&#xff0c;上传一个.html然后抓包改类型试试 上传成功了&#xff0c;但是提示‘<&…

C++/stack_queue

目录 1.stack 1.1stack的介绍 1.2stack的使用 练习题&#xff1a; 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

一、问题描述 运行网关微服务与用户微服务后&#xff0c;nacos服务成功注册 但是测试接口的时候网关没有找到相关服务 二、解决方案 我先检查了pom文件确定没问题后查看配置文件 最后发现是配置里spring.application.namexxx-user里面服务的名字后面多了一个空格 三、总结…

QT:图像上绘制图形

需求描述 1、展示一张图像 2、在图像上可以使用数据绘制图像&#xff1a;矩形、不规则图形、线条 3、有按键可以选择 概要设计 规划布局如下 1、左边是Qlabel 用于展示图片 2、右边是三个按钮 具体实现 1、 首先设计 UI 界面&#xff0c;对控件进行布局 在 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…

日志收集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…

群晖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…

ResNeSt: Split-Attention Networks论文学习笔记

这张图展示了一个名为“Split-Attention”的神经网络结构&#xff0c;该结构在一个基数组&#xff08;cardinal group&#xff09;内进行操作。基数组通常指的是在神经网络中处理的一组特征或通道。图中展示了如何通过一系列操作来实现对输入特征的注意力机制。 以下是图中各部…

【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…

Qt中QVariant的使用

1.使用QVariant实现不同类型数据的相加 方法&#xff1a;通过type函数返回数值的类型&#xff0c;然后通过setValue来构造一个QVariant类型的返回值。 函数&#xff1a; QVariant mainPage::dataPlus(QVariant a, QVariant b) {QVariant ret;if ((a.type() QVariant::Int) &a…

基础项目实战——3D赛车(c++)

目录 前言一、渲染引擎二、关闭事件三、梯形绘制四、轨道绘制五、边缘绘制六、草坪绘制七、前后移动八、左右移动​九、曲线轨道​十、课山坡轨道​十一、循环轨道​十二、背景展示​十三、引入速度​十四、物品绘制​十五、课数字路障​十六、分数展示​十七、重新生成​十八、…

【SpringBoot教程】Spring Boot + MySQL + HikariCP 连接池整合教程

&#x1f64b;大家好&#xff01;我是毛毛张! &#x1f308;个人首页&#xff1a; 神马都会亿点点的毛毛张 在前面一篇文章中毛毛张介绍了SpringBoot中数据源与数据库连接池相关概念&#xff0c;今天毛毛张要分享的是关于SpringBoot整合HicariCP连接池相关知识点以及底层源码…

一文讲解Java中Object类常用的方法

在Java中&#xff0c;经常提到一个词“万物皆对象”&#xff0c;其中的“万物”指的是Java中的所有类&#xff0c;而这些类都是Object类的子类&#xff1b; Object主要提供了11个方法&#xff0c;大致可以分为六类&#xff1a; 对象比较&#xff1a; public native int has…

操作系统之输入输出

&#x1f9d1; 博主简介&#xff1a;CSDN博客专家&#xff0c;历代文学网&#xff08;PC端可以访问&#xff1a;https://literature.sinhy.com/#/literature?__c1000&#xff0c;移动端可微信小程序搜索“历代文学”&#xff09;总架构师&#xff0c;15年工作经验&#xff0c;…

【Convex Optimization Stanford】Lec3 Function

【Convex Optimization Stanford】Lec3 Function 前言凸函数的定义对凸函数在一条线上的限制增值扩充&#xff1f; 一阶条件二阶条件一些一阶/二阶条件的例子象集和sublevel set关于函数凸性的扩展&#xff08;Jesen Inequality)保持函数凸性的操作非负加权和 & 仿射函数的…

【Linux探索学习】第二十七弹——信号(一):Linux 信号基础详解

Linux学习笔记&#xff1a; https://blog.csdn.net/2301_80220607/category_12805278.html?spm1001.2014.3001.5482 前言&#xff1a; 前面我们已经将进程通信部分讲完了&#xff0c;现在我们来讲一个进程部分也非常重要的知识点——信号&#xff0c;信号也是进程间通信的一…

SpringBoot或SpringAI对接DeekSeek大模型

今日除夕夜&#xff0c;deepseek可是出尽了风头&#xff0c;但是我看网上还没有这方面的内容对接&#xff0c;官网也并没有&#xff0c;故而本次对接是为了完成这个空缺 我看很多的博客内容是流式请求虽然返回时正常的&#xff0c;但是他并不是实时返回&#xff0c;而是全部响应…

Funnel-Transformer:通过过滤序列冗余实现高效语言处理

摘要 随着语言预训练的成功&#xff0c;开发更具扩展性且能高效利用大量未标注数据的架构变得尤为重要。为了提高效率&#xff0c;我们研究了在维持完整token级别表示时的冗余问题&#xff0c;尤其是对于仅需要序列单向量表示的任务。基于这一直觉&#xff0c;我们提出了Funne…

【搜索回溯算法】:BFS的魔力--如何使用广度优先搜索找到最短路径

✨感谢您阅读本篇文章&#xff0c;文章内容是个人学习笔记的整理&#xff0c;如果哪里有误的话还请您指正噢✨ ✨ 个人主页&#xff1a;余辉zmh–CSDN博客 ✨ 文章所属专栏&#xff1a;搜索回溯算法篇–CSDN博客 文章目录 一.广度优先搜索&#xff08;BFS&#xff09;解决最短路…

【算法】经典博弈论问题——威佐夫博弈 python

目录 威佐夫博弈(Wythoff Game)【模板】 威佐夫博弈(Wythoff Game) 有两堆石子&#xff0c;数量任意&#xff0c;可以不同&#xff0c;游戏开始由两个人轮流取石子 游戏规定&#xff0c;每次有两种不同的取法 1)在任意的一堆中取走任意多的石子 2)可以在两堆中同时取走相同数量…