当前位置: 首页 > news >正文

【Linux仓库】进程等待【进程·捌】

 

🌟 各位看官好,我是egoist2023!

🌍 Linux == Linux is not Unix !

🚀 今天来学习Linux的指令知识,并学会灵活使用这些指令。

👍 如果觉得这篇文章有帮助,欢迎您一键三连,分享给更多人哦!

目录

进程等待必要性

进程等待

等待方法

wait等待

waitpid等待(最佳实践)

获取子进程退出码

第二种获取子进程退出码:

 strerror 

获取子进程信号编号

非阻塞等待

为何释放时不能立即释放task_struct


进程等待必要性

  • 之前讲过,⼦进程退出,⽗进程如果不管不顾,就可能造成‘僵⼫进程’的问题,进⽽造成内存泄漏。
  • 另外,进程⼀旦变成僵⼫状态,那就⼑枪不⼊,“杀⼈不眨眼”的kill -9 也⽆能为⼒,因为谁也没有办法杀死⼀个已经死去的进程
  • 最后,⽗进程派给⼦进程的任务完成的如何,我们需要知道。如,⼦进程运⾏完成,结果对还是不对,或者是否正常退出。(如何判断子进程把任务完成怎么样? --> 退出码,这也是为什么要讲进程终止的原因,是为了进程等待服务)
  • 父进程通过进程等待的方式,回收子进程资源(必要),获取⼦进程退出信息(可选)。

进程等待

父进程创建子进程后,将来子进程执行完程序后,子进程进程终止,此时子进程的代码和数据会被释放,但是PCB不能立马释放;父进程通过等待的方式来回收子进程PCB(如果父进程没有回收,那么子进程就会处于 ' Z ' 僵尸状态), 如果父进程有需要的话也可以获取子进程的退出信息。

等待方法

wait等待

pid_t wait(int* status)

返回值:

        成功返回被等待进程pid,失败返回-1。

参数:

        输出型参数,获取⼦进程退出状态,不关⼼则可以设置成为NULL。 

如果子进程执行完自己的代码后,父进程迟迟不进行回收,那么我们应该会观察到子进程处于 ' Z ' 状态,那该如何模拟出子进程 ' Z ' 状态呢?只要让父进程一直死循环执行,不去回收子进程即可

waitpid等待(最佳实践)

pid_ t waitpid(pid_t pid, int *status, int options)

返回值:

        当正常返回的时候waitpid返回收集到的⼦进程的进程ID;

        如果设置了选项WNOHANG,而调⽤中waitpid发现没有已退出的⼦进程可收集,则返回0;

        如果调⽤中出错,则返回-1,这时errno会被设置成相应的值以指⽰错误所在;

参数:

        pid:

                Pid = -1,等待任⼀个⼦进程。与wait等效。

                Pid > 0.等待其进程ID与pid相等的⼦进程。

当pid设为-1的时候,父进程是如何获取所有子进程信息的呢?在task_struct存在一个链表成员,就是children成员用于维护进程的子进程链表.

struct task_struct
{struct list_head children;
}

        status: 输出型参数

                WIFEXITED(status): 若为正常终⽌⼦进程返回的状态,则为真。(查看进程是否是正常退出)

                WEXITSTATUS(status): 若WIFEXITED⾮零,提取⼦进程退出码(查看进程的

退出码)

options:默认为0,表⽰阻塞等待.

                WNOHANG: 若pid指定的⼦进程没有结束,则waitpid()函数返回0,不予以等待若正常结束,则返回该⼦进程的ID。

  • 如果⼦进程已经退出,调⽤wait/waitpid时,wait/waitpid会⽴即返回,并且释放资源,获得子进程退出信息。
  • 如果在任意时刻调⽤wait/waitpid,⼦进程存在且正常运⾏,则父进程可能阻塞。
  • 如果不存在该子进程,则⽴即出错返回。
获取子进程退出码
  • wait和waitpid,都有⼀个status参数,该参数是⼀个输出型参数,由操作系统填充。
  • 如果传递NULL,表⽰不关心子进程的退出状态信息。
  • 否则,操作系统会根据该参数,将子进程的退出信息反馈给⽗进程。

在下面这段程序中,父进程创建了10个子进程后,后续需要等待回收子进程的资源,并且父进程关心子进程的退出信息(是否出错),那么通过打印子进程的状态来查看,理论上应该能看到打印了status:115。

不对啊???子进程的退出码不是115吗?为什么变成256了呢?这里可以肯定的是status不仅仅是退出码。

status不能简单的当作整形来看待,可以当作位图来看待,具体细节如下图(只研究status低16比特位):

由上图可知,我们要拿到子进程的退出状态,那么就需要拿到次低8位的退出状态,通过位运算:

(status >> 8)& 0xFF; //0xFF --> 8个1 

第二种获取子进程退出码:

WEXITSTATUS能从wait系列函数返回的status中提取有效退出码,帮助父进程了解子进程的执行结果。

 strerror 
    std::vector<pid_t> subids;for(int i=0;i<N;i++){pid_t id = fork();if(id==0){printf("我是一个子进程,pid:%d ,ppid:%d,count:%d\n",getpid(),getppid(),i);sleep(1);break;}printf("子进程退出了\n");exit(1);subids.push_back(id);}sleep(5);//父进程执行流for(auto& sid:subids){int status =0;printf("父进程开始等待子进程ing...,%d\n",sid);pid_t rid =waitpid(sid,&status,0);if(rid>0){int exit_code=(status>>8)&0xFF;int exit_signal=status&0x7F;if(exit_code>0&&exit_signal==0){printf("子进程运行完毕,结果不正确:%d:%s\n",exit_code,strerror(exit_code));}}}

在上面这段程序当中,为什么权限会被拒绝了呢? 

 “权限被拒绝” 的根源:子进程的退出码 / 信号编号恰好等于EPERMerrno(1),导致strerror误解析。 

获取子进程信号编号

如果子进程出现异常了呢?那么父进程就不关心进程的退出码,因为出现了问题,导致OS收到了信号,杀掉了该进程,因此要关心的是为什么出现异常?那么,该如何拿到该信号数字呢?

kill 命令:

选项:

      -l : 显示信号数字

可以看到没有信号0,这也符合我们status图所述情况。

status & 0x7F; //0x7F --> 7个1 , 获取子进程信号

   for(int i=0;i<N;i++){pid_t id = fork();if(id==0){int i=10;while(i){printf("我是一个子进程,pid:%d ,ppid:%d,count:%d\n",getpid(),getppid(),i);sleep(1);i--;}printf("子进程退出了\n");exit(0);}}printf("父进程开始等待子进程ing...\n");sleep(5);//父进程执行流for(int i=0;i<N;i++){int status =0;pid_t rid =waitpid(-1,&status,0);if(rid>0){printf("父进程等待子进程成功,子进程pid:%d,status:%d,status code:%d,status singal:%d\n",rid,status,(status>>8)&0xFF,status&0x7F);}}

那如果我向子进程发出 信号9 呢? 子进程会异常终止,父进程回收到子进程的资源,通过status输出型参数获得子进程的 信号 ,进行打印验证是否为 信号9 。

 综上所述:

 进程正常结束: status = 退出码 + 0(信号编号)

 进程异常结束: status = 信号编号(退出码无意义) 

非阻塞等待

非阻塞等待:本质其实是检测子进程状态是否退出,若没有退出不会因为条件没有就绪而阻塞,而是立即返回。

pid_ t waitpid(pid_t pid, int *status, int options)

而要设为非阻塞等待,options要为WNOHANG,表示不要卡住,即非阻塞等待。

waitpid的返回值:

  1. 子进程退出 && waitpid 成功 --> 返回子进程pid
  2. 子进程没有退出 && waitpid成功 --> 返回0
  3. waitpid等待失败(如等待子进程不是你的) --> 返回-1
    pid_t id = fork();if(id==0){int cnt=10;while(cnt--){printf("子进程在运行:%d\n",cnt);sleep(1);}exit(0);}//父进程pid_t rid = waitpid(id,NULL,WNOHANG);if(rid==id){printf("wait child success\n");break;}else if(rid==0){printf("child note quit\n");sleep(1);}else{printf("wait error\n");break;}

在上面这段程序:父进程fork创建子进程,此时父子进程各自执行自己的执行流,子进程在执行while循环的时候,父进程非阻塞等待子进程,但子进程还没退出,此时pid_t 的返回值为 0 ,因此会打印 rid == 0 的条件内容,父进程提前退出。

同时会发现子进程用ctrl + c 退出不了,只有等自己的进程结束后才会终止,这又是为什么呢?这里使用ps工具进行观察。

由于父进程的提前退出,导致子进程变成孤儿进程,而我们前面说过孤儿进程会被1号进程所领养,即被bash所领养。为什么无法 ctrl + c 终止呢?孤儿进程的父进程变为 init/systemd,而Ctrl + C的信号传递依赖原进程组结构,导致信号无法有效作用于孤儿进程。

那真正的非阻塞轮询该怎样做才有意义?又是怎样的呢?

很明显,父进程在非阻塞等待子进程的同时也可以做做其他事情

Task.hpp

#pragma once#include<iostream>void Download()
{std::cout<<"我是一个下载任务\n"<<std::endl;
}void Printlog()
{std::cout<<"我是一个打印日志任务\n"<<std::endl;
}void FlushData()
{std::cout<<"我是一个刷新数据的任务\n"<<std::endl;
}

Tool.hpp 

#pragma once#include<iostream>
#include<vector>
#include<functional>using func_t = std::function<void()>;
//typedef functional<void()> func_t;class Tool
{
public:Tool(){}void PushFunc(func_t f){_funcs.push_back(f);}void Execute(){for(auto& f:_funcs){f();}}~Tool(){}
private:std::vector<func_t> _funcs; //方法集
};

 myproc.cc

#include<stdlib.h>
#include<unistd.h>
#include<stdio.h>
#include<sys/wait.h>
#include"Task.hpp"
#include"Tool.hpp"int main()
{//方法集Tool tool;tool.PushFunc(Download);tool.PushFunc(Printlog);tool.PushFunc(FlushData);pid_t id = fork();if(id==0){int cnt=10;while(cnt--){printf("子进程在运行:%d\n",cnt);sleep(1);}exit(0);}//父进程while(1){pid_t rid = waitpid(id,NULL,WNOHANG);if(rid==id){printf("wait child success\n");break;}else if(rid==0){printf("child note quit\n");//做做其他事情tool.Execute();sleep(1);}else{printf("wait error\n");break;}}return 0;
}

为何释放时不能立即释放task_struct


http://www.lryc.cn/news/626862.html

相关文章:

  • AI on Mac, Your Way!全本地化智能代理,隐私与性能兼得
  • SQL详细语法教程(七)核心优化
  • 【C语言16天强化训练】从基础入门到进阶:Day 4
  • Android 资源替换:静态替换 vs 动态替换
  • 猫头虎开源AI分享|基于大模型和RAG的一款智能text2sql问答系统:SQLBot(SQL-RAG-QABot),可以帮你用自然语言查询数据库
  • Https之(二)TLS的DH密钥协商算法
  • FFmpeg的基本概述(二)
  • 基于 Java 和 MySQL 的精品课程网站
  • 零知开源——基于STM32F103RBT6与ADXL362三轴加速度计的体感迷宫游戏设计与实现
  • AV1视频编码器2024-2025技术进展与行业应用分析
  • 全球首款 8K 全景无人机影翎 A1 发布解读:航拍进入“先飞行后取景”时代
  • 《算法导论》第 33 章 - 计算几何学
  • 189.轮转数组
  • Linux多线程——线程池
  • Dubbo 的 Java 项目间调用的完整示例
  • 新手向:Python实现文件加密解密工具
  • 【java面试day16】mysql-覆盖索引
  • 害虫检测识别数据集:近4K图像,6类,yolo标注
  • 【CocosCreator】electron/Cocos双窗口本地模拟聊天系统
  • Spring事务源码
  • PyTorch API 1
  • 【数据结构】递归与非递归:归并排序全解析
  • 第一章:认识 CAD 图形文件 —— DXF 格式
  • 车载软件架构 --- 赢得汽车软件开发竞赛
  • 好家园房产中介网后台管理完整(python+flask+mysql)
  • Scikit-learn 预处理函数分类详解
  • 【Task02】:四步构建简单rag(第一章3节)
  • 第R6周:LSTM实现糖尿病探索与预测
  • 深度学习核心技巧与实战指南
  • 机器学习中的数据处理技巧