当前位置: 首页 > 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.dtcms.com/a/340663.html

相关文章:

  • week3-[分支嵌套]方阵
  • React15.x版本 子组件调用父组件的方法,从props中拿的,这个方法里面有个setState,结果调用报错
  • setup 函数总结
  • 买卖股票的最佳时机III
  • C++STL-list 底层实现
  • Adobe Adobe Illustrator Ai 2025最新版软件安装包下载与详细图文安装教程!!
  • 代码随想录Day57:图论(寻宝prim算法精讲kruskal算法精讲)
  • 【自动化运维神器Ansible】Roles中Tags使用详解:提升自动化效率的利器
  • STM32 外设驱动模块五:DHT11 温湿度传感器
  • 【Express零基础入门】 | 构建简易后端服务的核心知识
  • 如何查看和修改网络接口参数?
  • 计算机网络模型
  • 2025年Java后端最新场景题+八股文面试题
  • 田野科技“一张皮”,“AI+虚拟仿真”推动考古教学创新发展
  • 晨控EtherCAT设备分配IP操作手册
  • 详细的Git的安装教程
  • 运用平均值填充后的数据进行模型预测
  • 豆秒数科集团:汽车消费金融市场的领跑者
  • Linux中Cobbler服务部署与配置(快速部署和管理 Linux 系统)
  • TheadLocal相关
  • E10 通过RPC实现账号批量锁定与解锁
  • Json转txt
  • CTFshow系列——命令执行web38-40
  • 五种算法详解(SVM / Logistic Regression / kNN / Random Forest / HistGradientBoosting)
  • 无人机抗噪模块技术概述!
  • 20.web api 11
  • C5.6:双电源发射极偏置、特殊类偏置、PNP型偏置电路
  • 如何快速上手【Spring AOP】?核心应用实战(上篇)
  • 【买机器人,上BFT】香港大学联合项目论文解读 |Bunny-VisionPro:用于模仿学习的低成本实时双臂灵巧遥操作系统
  • SpringBoot 整合 Langchain4j RAG 技术深度使用解析