《Linux从练气到飞升》No.19 进程等待

🕺作者: 主页

我的专栏
C语言从0到1
探秘C++
数据结构从0到1
探秘Linux
菜鸟刷题集

😘欢迎关注:👍点赞🙌收藏✍️留言

🏇码字不易,你的👍点赞🙌收藏❤️关注对我真的很重要,有问题可在评论区提出,感谢阅读!!!

目录

前言

在操作系统中,进程等待是一种关键的机制,用于实现进程之间的同步和协作。通过等待子进程的结束并获取其退出状态,父进程可以控制程序的执行顺序和处理子进程的结果。本篇博客将介绍进程等待的原理和用法,帮助读者深入理解进程间通信的重要概念和技术。

进程等待必要性

  • 之前讲过,子进程退出,父进程如果不管不顾,就可能造成‘僵尸进程’的问题,进而造成内存泄漏。
  • 另外,进程一旦变成僵尸状态,那就刀枪不入,“杀人不眨眼”的kill -9 也无能为力,因为谁也没有办法杀死一个已经死去的进程。
  • 最后,父进程派给子进程的任务完成的如何,我们需要知道。如果,子进程运行完成,结果对还是不对,或者是否正常退出。
  • 父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息

模拟僵尸进程

在我们讲述进程状态的时候,我们讲述过僵尸进程指的是:子进程退出,父进程不管不顾

模拟代码:

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>int code = 0;int main()
{pid_t id = fork();if(id < 0){perror("fork");exit(1); //标识进程运行完毕,结果不正确}else if(id == 0){//子进程int cnt = 5;while(cnt){printf("cnt: %d, 我是子进程, pid: %d, ppid : %d\n", cnt, getpid(), getppid());sleep(1);cnt--;}}else{//父进程printf("我是父进程, pid: %d, ppid: %d\n", getpid(), getppid());sleep(7);}
}

运行结果:

 查看状态的bash命令:

while :; do ps ajx | head -1 && ps ajx | grep mycode | grep -v grep; sleep 1; echo "-----------------------"; done

查看状态:

 模拟成功!

进程等待的方法

子进程被创建出来,谁先运行,是有调度器说了算的。

那么谁先退出呢? 一般而言,我们通常要让子进程先退出。

为甚?

因为父进程可以很容易对子进程进行管理(垃圾回收)、处理业务,需要让父进程帮我们拿到子进程执行的结果。

一般子进程是需要被等待的,被父进程等,wait/waitpid.
 

wait方法

是什么?

是父进程通过wait等系统调用,用来等待子进程状态的一种现象,是必须的

为什么?

1.防止子进程发生僵尸问题,进而产生内存泄漏

2.读取子进程状态

怎么办?

wait/waitpid, status (signal, exit code).
 

#include<sys/types.h>
#include<sys/wait.h>
pid_t wait(int*status);
返回值:
成功返回被等待进程pid,失败返回-1。
参数:
输出型参数,获取子进程退出状态,不关心则可以设置成为NULL

 参数:
        输出型参数:将wait函数内部计算的结果通过status返回给调用者
        输入型参数:调用者给被调用函数的传参


输入输出型参数编码的时候,小小的代码规范
        输入型:给引用
        输入输出,输出型参: 给指针

测试代码

#include <stdio.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <unistd.h>int main()
{pid_t pid = fork();if (pid < 0){printf("fork error\n");}else if (pid == 0){int count = 0;while (1){sleep(1);printf("i am child\n");if (count == 3){break;}count++;}exit(0);}else{int count = 0;while (1){sleep(1);printf("i am father\n");while (count == 5){wait(NULL);}count++;}exit(0);}return 0;
}

运行结果

 

waitpid方法

pid_ t waitpid(pid_ _t pid, int *status, int options) ;
        pid :
                Pid=-1,等待任一个子进程。与wait等效。
                Pid>0,等待其进程ID与pid相等的子进程。
.
        status :同wait
        options :
                0 :阻塞模式
                WNOHANG :非阻塞 模式
                        非阻塞模式需要搭配循环使用

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相等的子进程。
status:
WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)
WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)
options:
WNOHANG: 若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则返回该子进
程的ID。
  • 如果子进程已经退出,调用wait/waitpid时,wait/waitpid会立即返回,并且释放资源,获得子进程退出信息。
  • 如果在任意时刻调用wait/waitpid,子进程存在且正常运行,则进程可能阻塞。
  • 如果不存在该子进程,则立即出错返回。

测试代码

#include <stdio.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdlib.h>int main()
{pid_t pid = fork();if (pid < 0){printf("fork error!\n");}else if (pid == 0){//childint count = 0;while (count < 5){printf("child is running, pid=%d\n", getpid());sleep(1);count++;}exit(0);}else{//fatherprintf("father wait before!\n");pid_t ret = waitpid(pid, NULL, 0);if (ret > 0){printf("wait success!\n");}else{printf("wait failed\n");}printf("father wait after!\n");}return 0;
}

运行结果

看下面结果图发现当父进程调用了waitpid函数时父进程就被阻塞了,阻塞期间当子进程运行完毕父进程才执行完毕,所以只有子进程退出了父进程才会退出,那么子进程就一定不是僵尸进程。

 获取子进程status

pid_ t waitpid(pid_t pid, int *status, int options);
status:是一个整形指针,其实在传参的时候,该参数是一个输出型参数!


int st=0;
waitpid(pid, &st, 0); //开始等待,子进程退出,操作系统就会从进程PCB中读取退出信息,保存在status指向的变量中

返回之后,st中就保存的是我们进程退出的信息,int 是32bit,是否正常运行,退出码
是多少,退出信号是多少。
 

  • wait和waitpid,都有一个status参数,该参数是一个输出型参数,由操作系统填充。
  • 如果传递NULL,表示不关心子进程的退出状态信息。
  • 否则,操作系统会根据该参数,将子进程的退出信息反馈给父进程。
  • status不能简单的当作整形来看待,可以当作位图来看待,具体细节如下图(只研究status低16比特位):
    • 次低8位表示子进程退出码
    • 最低7个比特位表示进程收到的信号

//测试代码:
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
int main( void )
{pid_t pid;if ( (pid=fork()) == -1 )perror("fork"),exit(1);if ( pid == 0 ){sleep(20);exit(10);} else {int st;int ret = wait(&st);if ( ret > 0 && ( st & 0X7F ) == 0 ){ // 正常退出printf("child exit code:%d\n", (st>>8)&0XFF);} else if( ret > 0 ) { // 异常退出printf("sig code : %d\n", st&0X7F );}}    
}

 测试exit code,exit signal

#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdlib.h>int main()
{pid_t pid = fork();if (pid < 0){printf("fork error!\n");}else if (pid == 0){//childint count = 0;while (count < 5){printf("child is running, pid=%d\n", getpid());sleep(1);count++;}exit(0);}else{//fatherprintf("father wait before\n");int st = 0;pid_t ret = waitpid(pid, &st, 0);if (ret > 0){printf("wait success!\n");printf("st=%d\n", st);printf("child exit signal=%d\n", st & 0x7f);printf("child exit code=%d\n", (st >> 8) & 0xff);}if (st & 0x7F){printf("child run error!\n");}else{int code = (st >> 8) & 0xff;if (code){printf("child run success, but result is not right: code=%d\n", code);}else{printf("child run success, and result is right: code=%d\n", code);}}}printf("wait after!\n");return 0;
}

 

1.父进程通过wait/waitpid可以拿到子进程的退出结果,为什么要用wait/waitpid函数呢?直接全局变量不行吗?

进程具有独立性,那么数据就要发生写时拷贝,父进程无法拿到,况且,信号呢? ?

2. 既然进程是具有独立性的,进程退出码,不也是子进程的数据吗? ?父进程又凭什么拿到呢? ?wait/waitpid究竟干了什么呢? 

首先要知道僵尸进程至少要保留该进程的PCB信息!

task_struct里面保留了任何进程退出时的退出结果信息。

wait/waitpid 本质其实是读取子进程的task_struct结构 ,

task_struct 里面包含了: 【int exit_ code, exit_ signal;】

3.wait/waitpid有这个权利吗?

有,可以系统调用! ,不就是操作系统吗! ! task_ struct 是内核数据结构对象! !

阻塞与非阻塞

  • 阻塞等待是指一个任务在等待某个操作完成时,会被挂起,暂停执行直到操作完成后再继续执行。在阻塞等待期间,该任务无法进行其他的工作。
  • 非阻塞等待是指一个任务在等待某个操作完成时,会使用轮询或回调的方式不断查询操作状态,可以继续执行其他任务。非阻塞等待不会让一个任务暂停执行,即使操作未完成。
  • 两者的区别在于任务在等待某个操作完成时的行为表现:
    • 阻塞等待会暂停任务的执行,直到操作完成。
    • 非阻塞等待允许任务继续执行,并对操作状态进行查询或设置回调函数。
  • 具体区别如下:
    • 阻塞等待会造成任务阻塞,无法进行其他操作,而非阻塞等待允许任务继续执行其他操作。
    • 阻塞等待的操作结果通常是通过阻塞等待的方式获取,而非阻塞等待需要主动轮询或回调来获取操作结果。
    • 阻塞等待的效率较低,因为任务可能需要等待较长时间才能继续执行,而非阻塞等待可以提高任务的响应速度和并发性。
    • 阻塞等待通常使用在同步模式下,保证任务的执行顺序;非阻塞等待则常用于异步模式下,充分利用系统资源。

进程的阻塞等待方式

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
int main()
{
pid_t pid;
pid = fork();
if(pid < 0){
printf("%s fork error\n",__FUNCTION__);
return 1;
} else if( pid == 0 ){ //child
printf("child is run, pid is : %d\n",getpid());
sleep(5);
exit(257);
} else{
int status = 0;
pid_t ret = waitpid(-1, &status, 0);//阻塞式等待,等待5S
printf("this is test for wait\n");
if( WIFEXITED(status) && ret == pid ){
printf("wait child 5s success, child return code is :%d.\n",WEXITSTATUS(status));
}else{
printf("wait child failed, return.\n");
return 1;
}
}
return 0;
}

 进程的非阻塞等待方式

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
int main()
{
pid_t pid;
pid = fork();
if(pid < 0){
printf("%s fork error\n",__FUNCTION__);
return 1;
}else if( pid == 0 ){ //child
printf("child is run, pid is : %d\n",getpid());
sleep(5);
exit(1);
} else{
int status = 0;
pid_t ret = 0;
do
{
ret = waitpid(-1, &status, WNOHANG);//非阻塞式等待
if( ret == 0 ){
printf("child is running\n");
}
sleep(1);
}while(ret == 0);
if( WIFEXITED(status) && ret == pid ){
printf("wait child 5s success, child return code is :%d.\n",WEXITSTATUS(status));
}else{
printf("wait child failed, return.\n");
return 1;
}
}
return 0;
}

后记

本篇讲述了进程等待的相关知识。

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Matlab图像处理-灰度插值法

最近邻法 最近邻法是一种最简单的插值算法&#xff0c;输出像素的值为输入图像中与其最邻近的采样点的像素值。是将(u0,v0)(u_0,v_0)点最近的整数坐标u,v(u,v)点的灰度值取为(u0,v0)(u_0,v_0)点的灰度值。 在(u0,v0)(u_0,v_0)点各相邻像素间灰度变化较小时&#xff0c;这种方…

Vue/React 项目部署到服务器后,刷新页面出现404报错

问题描述&#xff1a;在本地启动项目一切正常&#xff0c;部署到服务器上线后出现BUG&#xff0c;项目刷新页面出现404。 起初以为是自己路由守卫或是token丢失问题&#xff0c;找了一圈终于解决了 产生原因&#xff1a;我们打开vue/react打包后生成的dist文件夹&#xff0c;可…

day27 String类 正则表达式

String类的getBytes方法 String s "腻害"; byte[] bytes s.getBytes(StandardCharsets.UTF_8); String类的new String方法 String ss "ss我的"; byte[] gbks ss.getBytes("gbk"); String gbk new String(gbks, "gbk"); String类的…

jmeter源码二次开发

本文以jmeter5.5为例&#xff0c;扩展“TCP Socket支持定长的返回字节流”功能。 一、 源码本地编译运行 1、在jmeter官网下载源码&#xff1a;jmeter各版本源码地址 2、在idea中用gradle导入jmeter源码&#xff0c;idea中要配置jdk&#xff0c;gradle&#xff0c;我用的是j…

Leetcode213 打劫家舍2

思路&#xff1a;既然头尾不能同时取&#xff0c;那就分别算只取头或者只取尾&#xff0c;不考虑特殊情况的话是一个简单的动态规划 class Solution:def rob(self, nums: list[int]) -> int:if len(nums) < 3:return max(nums)max_sum [nums[0], max(nums[1], nums[0])…

NoSQL基础

零、文章目录 NoSQL基础 1、为什么要用NoSQL &#xff08;1&#xff09;单机单数据库时代 网络不发达的时代&#xff0c;一个系统由一台机器&#xff0c;一个数据库来实现&#xff0c;只要流量上来&#xff0c;首先性能瓶颈就是数据库IO。 &#xff08;2&#xff09;Cache数…

掌握逻辑漏洞复现技术,保护您的数字环境

环境准备 这篇文章旨在用于网络安全学习&#xff0c;请勿进行任何非法行为&#xff0c;否则后果自负。 1、支付逻辑漏洞 攻击相关介绍 介绍&#xff1a; 支付逻辑漏洞是指攻击者利用支付系统的漏洞&#xff0c;突破系统的限制&#xff0c;完成非法的支付操作。攻击者可以采…

Docker部署RustDesk Server 设置开机自启

三、Docker安装 Docker官方和国内daocloud都提供了一键安装的脚本&#xff0c;使得Docker的安装更加便捷。 官方的一键安装方式&#xff1a; curl -fsSL https://get.docker.com | bash -s docker --mirror Aliyun 国内 daocloud一键安装命令&#xff1a; curl -sSL https://…

Unity 之 参数类型之值类型参数的用法

文章目录 基本数据类型结构体结构体的进一步补充 总结&#xff1a; 当谈论值类型参数时&#xff0c;我们可以从基本数据类型和结构体两个方面详细解释。值类型参数指的是以值的形式传递给函数或方法的数据&#xff0c;而不是引用。 基本数据类型 基本数据类型的值类型参数&…

LTE ATTACH流程、PDN流程、PGW地址分配介绍

1、S-GW\P-GW选择 MME根据S-GW和P-GW的拓扑信息进行S-GW/P-GW的选择&#xff0c;在S-GW的候选序列和P-GW的候选序列中比较&#xff0c;寻找是否有合一的S-GW/P-GW&#xff0c;并且根据S-GW的优先级和权重信息进行排序&#xff0c;得到S-GW/P-GW的候选组。 2、SGW>PGW连接 PD…

C++ 面试题(一)--C++基础,面向对象,内存管理

目录 1.part1 C基础 1 C特点 2 说说C语言和C的区别 3 说说 C中 struct 和 class 的区别 4 include头文件的顺序以及双引号""和尖括号<>的区别 5 说说C结构体和C结构体的区别 6 导入C函数的关键字是什么&#xff0c;C编译时和C有什么不同&#xff1f; 7…

leetcode121. 买卖股票的最佳时机

题目&#xff1a; 给定一个数组 prices &#xff0c;它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。 你只能选择 某一天 买入这只股票&#xff0c;并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。 返回你可以从这笔交易…