[Linux]进程等待

文章目录

  • 3.进程等待
    • 3.1什么是进程等待
    • 3.2为什么要进程等待
    • 3.3如何进行进程等待?
      • 1.wait
      • 2.waitpid
        • 2.1函数的讲解
        • 2.2status的理解
        • 2.3代码理解
    • 3.4学后而思
      • 1.直接用全局变量获取子进程退出码可以吗?如下
      • 2.进程具有独立性 退出码是子进程的数据 父进程是如何拿到退出码的
      • 3.对内存泄露的认识
      • 4.对于上文中提到的系统设置的宏
      • 5.阻塞等待和非阻塞等待
      • 6.status相关宏加入后的简便写法
      • 7.非阻塞式等待的实现

3.进程等待

3.1什么是进程等待

进程等待: 进程的一种状态 父进程等待子进程退出时的一个过程

3.2为什么要进程等待

进程退出时 会关闭所有的文件描述符 释放在内存中的代码和数据 内核数据结构task_struct会暂时保留 里面存放着进程的退出状态以及统计信息等 父进程创建子进程 让子进程来处理事务 父进程需要得知子进程对任务的完成情况即上述的三种情况在这里插入图片描述且需要获取子进程的退出状态 如果子进程先于父进程退出 父进程则无法获取子进程的退出状态 子进程此时就会处于僵尸状态

所以进程等待的两大原因:

  1. 获取子进程的退出状态 避免出现僵尸进程 减少内存泄漏的概率[回收子进程资源]
  2. 获取子进程对任务的完成情况[获取子进程退出信息]

3.3如何进行进程等待?

在这里插入图片描述
在这里插入图片描述

1.wait

在这里插入图片描述

  1. 返回值:
    函数调用成功+目标进程成功改变状态 返回目标进程的pid
    失败返回-1
  2. 参数:
    输出型参数 获取子进程退出状态 不关心则可以设置成为NULL 表示不获取
#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--;}exit(0);     //子进程运行5s后正常退出}else{//父进程printf("我是父进程, pid: %d, ppid: %d\n", getpid(), getppid());sleep(6);pid_t ret = wait(NULL); //阻塞式的等待if (ret > 0){printf("成功等待子进程改变状态, ret = %d\n", ret);}while (1){printf("我是父进程, pid: %d, ppid: %d\n", getpid(), getppid());sleep(1);}}
}

在这里插入图片描述
在这里插入图片描述
当子进程运行5s完退出 父进程还在运行 此时子进程处于僵尸状态 当父进程运行了7s 父进程执行了wait()函数 我们可以理解为 父进程被调度到子进程的后面或阻塞队列 直到子进程从僵尸状态改变状态为终止态 父进程才继续运行 从man手册我们可以查到wait()函数成功等待返回值为被终止掉的子进程的PID 没有成功等待则返回-1

2.waitpid

2.1函数的讲解

waitpid(pid, NULL, 0) == wait(NULL);
在这里插入图片描述

返回值

  1. 正常返回 waitpid返回收集到的子进程的PID
  2. 没有已退出的子进程可收集即所有的子进程都还在运行 返回0 当options = WNOHANG
  3. 函数调用出错 返回-1 errno被设置成相应的值以指示错误原因(错误原因如目标子进程不存在)

pid_t pid:

传参数pid = -1 表示父进程要等待任意一个子进程 ==
传参数pid = Pid > 0 等待PID是Pid的进程

int* status:

  1. 输出型参数,由操作系统填充int status = 0; waitpid(pid, &status, 0); 操作系统会根据子进程PCB中的退出码和退出信号,将子进程的退出信息通过status反馈给父进程
  2. 如果传递NULL,表示不关心子进程的退出状态信息。
  3. 程序运行结果有三种 status并不是按照整数整体来使用的 它是按照比特位的方式 将32个比特位进行划分 此文只讲解低16位
  • 实际上为了更方便使用status 即不再是用位运算 OS提供了宏定义 调用即可
    WIFEXITED(status): wait if exited进程是否正常退出 若为正常终止子进程 返回真
    WEXITSTATUS(status): wait exit status获取进程退出码 若WIFEXITED非零==>即子进程正常退出(代码跑完了 不是被信号杀死的) 提取子进程退出码

int options:

默认为0 表示阻塞式等待
options = 1: 非阻塞式等待

  • 为了不再程序中写一些数字 通常用宏来指示特定含义
    如1就可用WNOHANG来指示 wait no hang 表示父进程进行非阻塞式等待
  1. 子进程都在运行 函数返回0 不进行持续等待
  2. 子进程正常退出 函数返回子进程PID
2.2status的理解

在这里插入图片描述
在这里插入图片描述

  1. 已知可以通过查看status来获取子进程的退出码进而得知子进程的运行结果/退出状态信息
  2. 简单来说 进程终止有两种方式 代码跑完和没跑完 代码跑完无论结果正确都叫正常终止 而如果是没跑完就终止即为异常终止 即程序崩溃或异常退出 (我们之前没学过进程 所以一直说程序崩溃或程序退出 其实正确的说法应为进程异常退出或进程崩溃)
  3. 进程崩溃/异常退出 本质是OS通过发信号的方式杀掉了进程 可以通过查看进程收到的信号编号来得知子进程收到了几号信号 如果进程崩溃/异常退出 退出码就没有意义 所以我们不仅要获取退出码 还要通过查看收到的信号编号是0(正常跑完) 1~31(异常/崩溃) 来判断退出码是否有意义
    在这里插入图片描述

OS都能发哪些信号呢? 1~31重点了解 32/33没有 34-64了解即可

在这里插入图片描述

  1. 父进程调用waitpid()函数之前定义一个int变量 调用waitpid()函数时 把这个变量地址传给waitpid()函数
  2. 这个函数不仅会等待子进程改变状态 还会把子进程的退出码和进程收到的信号编号以上图形式填充给你传过来的变量 即status 通过对status的位操作可以查看退出码和信号编号
  3. 程序异常,可能是内部代码有问题,也可能是外力杀掉 (子进程代码是否跑完是不确定的)
2.3代码理解
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>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--;}exit(15);}else{//父进程printf("我是父进程, pid: %d, ppid: %d\n", getpid(), getppid());int status = 0;pid_t ret = waitpid(id, &status, 0); //阻塞式的等待if (ret > 0){   int signal_number = status & 0x7F;int exit_code = (status >> 8) & 0xFF;printf("等待子进程改变状态, ret: %d, 子进程收到的信号编号: %d, 子进程退出码: %d\n", ret, signal_number, exit_code);}}
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
信号编号取最低7位: 按位与0000 0000 0000 0000 0000 0000 0111 1111
退出码取次低8位: 按位与0000 0000 0000 0000 0000 0000 1111 1111
跟1按位与: 值不变 是0的是0 是1的是1
跟0按位与: 值全变0

通过可以修改代码 可以看到对应的信号

 子进程死循环 外部 kill -9 子进程pid : 杀死子进程signal_number = 9(SIGKILL:signal kill)exit_code = 0 sn!=0 退出码无意义cnt--;signal_number = 11(SIGSEGV:signal segmentation violation)exit_code = 0 sn!=0 退出码无意义int * p = NULL;*p = 100;signal_number = 8(SIGFPE:signal float point error) exit_code = 0 sn!=0 退出码无意义int a = 10;a /= 0;

3.4学后而思

1.直接用全局变量获取子进程退出码可以吗?如下

#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--;}code = 15;exit(15);}else{//父进程printf("我是父进程, pid: %d, ppid: %d\n", getpid(), getppid());int status = 0;pid_t ret = waitpid(id, &status, 0); //阻塞式的等待if (ret > 0){   int signal_number = status & 0x7F;int exit_code = (status >> 8) & 0xFF;printf("等待子进程改变状态, ret: %d, 子进程收到的信号编号: %d, 子进程退出码: %d\n", ret, signal_number, exit_code);printf("code: %d\n", code);}}
}
  • 答案是不行. 已知父进程通过wait/waitpid可以拿到子进程的退出结果(退出码+收到的信号)
  • 1. 父进程 子进程均有全局变量code 初始值均为0 当子进程对code修改发生写时拷贝 父进程去访问code时是他自己的code仍为0
  • 2. 即便你显示传一个值把他作为退出码 你怎么直到他的退出码是几? 就是说 退出码是显示子进程的运行结果的 你不知道它结果正确或是发生错误 进而也就不知道他的退出码是几
  • 3. 即便你可以拿到退出码 也拿不到子进程收到的信号

2.进程具有独立性 退出码是子进程的数据 父进程是如何拿到退出码的

僵尸进程: 子进程已死亡等待父进程读取状态
在这里插入图片描述

  1. 僵尸进程的代码和数据已经释放但是PCB还存在 且 task_struct里的int exit_code, exit_signal:字段保留了进程退出时的退出码和退出信号(不仅是僵尸进程的PCB会保留 任何进程退出时都会把退出码和退出信号保留在)int exit_code, exit_signal:
  2. 那么系统调用接口wait/waitpid可以读取子进程的PCB里的int exit_code, exit_signal:把退出码和退出信号以位图形式存在status中 然后父进程就可以再反向获取
  3. 父进程没办法直接获取子进程的信息 但是可以通过调用系统接口来获取

3.对内存泄露的认识

  1. 用户写的C/C++程序中malloc/new的空间当进程终止 即便这些空间有泄露 OS也已经回收
  2. 子进程死亡父进程如果一直不读取子进程的退出状态 那么子进程将一直处于Z状态 子进程的PCB属于内核数据结构 需要OS来释放 如果父进程不管 将影响其他进程

4.对于上文中提到的系统设置的宏

  1. linux是用C语言写的 linux将自己一些系统调用接口二次封装成函数提供使用 除了一些系统调用接口/函数外 还有一些宏定义 比如WNOHANG
  2. 一些运维的程序员会说如这个进程hang住了之类的话 其实就是我们通常说的卡了 可能是在等待某种资源如网络/磁盘等 可能是进程太多了 CPU忙不过来了 这个进程hang住了 说这个进程hang住 就是说它要么在阻塞队列中 要么等待CPU调度

grep -ER 'WNOHANG' /usr/include/

grep -ER是一个Linux命令,用于在文件中递归搜索指定的字符串模式。其中,E表示使用扩展正则表达式,R表示递归搜索子目录。下面是一个例子:

假设我们有一个名为test.txt的文件,内容如下:

hello world
hello grep

我们可以使用grep -ER命令来搜索包含“hello”的行:

grep -ER "hello" test.txt

输出结果为:

test.txt:hello world
test.txt:hello grep

如果我们只想搜索以“h”开头的行,可以使用正则表达式“^h”:

grep -ER "^h" test.txt

输出结果为:

test.txt:hello world
test.txt:hello grep

如果我们想要搜索以“h”开头并且包含“o”的行,可以使用正则表达式“^h.*o”:

grep -ER "^h.*o" test.txt

输出结果为:

test.txt:hello world

5.阻塞等待和非阻塞等待

1先来回顾一下阻塞状态和挂起状态的知识

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  1. 阻塞等待一般是在内核中阻塞 等待资源就绪去完成自己的工作 而一个进程一直在阻塞即资源一直无法就绪 这个进程的代码和数据就被移到磁盘SWAP分区了 即此进程被切换到挂起状态了
  2. 非阻塞等待: 父进程调用waitpid来等待子进程 如果子进程没有退出 他不像阻塞等待那样一直等着直至父进程被挂起还在等 他会直接返回 这样父进程在子进程运行的这一段时间还可以做其他事情 只不过中途需要再去看看子进程运行状况(下面讲怎么中途查看)
  3. 关于网络部分的代码 大部分时IO类别 阻塞和非阻塞接口非常多
    在这里插入图片描述

对进程等待的认识

  1. 在阻塞式等待下 只有子进程退出的时候,waitpid函数才会进行返回父进程只是挂起 仍然存活
  2. waitpid/wait 让进程退出具有一定的顺序性 将来可以让父进程进行更多的收尾工作.

6.status相关宏加入后的简便写法

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>int main() {pid_t id = fork();if (id < 0) {printf("创建子进程失败!\n");exit(1);}else if (id == 0){printf("我是子进程: pid: %d,我将异常退出!\n", getpid());int* p = NULL;*p = 1; // 引发异常}else {printf("我是父进程: pid: %d,我将耐心地等待子进程!\n", getpid());int status = 0;pid_t ret = waitpid(id, &status, 0);if (ret > 0){// 等待成功//printf("父进程等待成功, 退出码: %d, 退出信号: %d\n", (status>>8)&0xFF, status & 0x7F);if (WIFEXITED(status)){printf("子进程正常退出,退出码: %d\n", WEXITSTATUS(status));}else if (WIFSIGNALED(status)) {printf("子进程异常退出,信号编号: %d\n", WTERMSIG(status));}}}return 0;}

7.非阻塞式等待的实现

当子进程未退出 父进程可以处理其他事务 父进程要处理的事务不是一次就确定好的 可能在日后会有新的事务添加进来 为了封装/降低耦合 可以设置指针数组/回调函数 只需要编写Load函数 就可以增加父进程的任务单

typedef void (*pfunc)(); //函数指针类型std::vector<pfunc> vfunc; //函数指针数组void fun_one()
{printf("临时任务1\n");
}
void fun_two()
{printf("临时任务2\n");
}void Load()
{vfunc.push_back(fun_one);vfunc.push_back(fun_two);
}int main()
{pid_t id = fork();if(id == 0){// 子进程int cnt =  5;while(cnt){printf("我是子进程: %d\n", cnt--);sleep(1);}exit(123); // 123: 测试 无意义}else{int quit = 0;while(!quit){int status = 0;pid_t res = waitpid(-1, &status, WNOHANG); //非阻塞方式等待if(res > 0){//函数调用成功 && 子进程已退出printf("等待子进程退出成功, 退出码: %d\n", WEXITSTATUS(status));quit = 1;}else if( res == 0 ){//函数调用成功 && 子进程未退出printf("子进程未退出,父进程可以处理其他事务\n");if(vfunc.empty()) Load();for(auto func : vfunc){func();}}else{//等待失败printf("函数调用失败!\n");quit = 1;}sleep(1);}}
}

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

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

相关文章

云匣子 FastJson反序列化RCE漏洞复现

0x01 产品简介 云匣子是租户连接云资源的安全管理工具&#xff0c;帮助云租户更加安全、精细的管理云上的虚拟机、数据库等资源。 云安宝结合多年的运维和安全实践&#xff0c;将云上的运维和安全有机结合&#xff0c;实现对运维过程的事前规划、事中控制和 事后审计。在此之上…

Handler系列-prepareMainLooper在哪里调用的

ActivityThread的main方法里调用prepareMainLooper public final class ActivityThread {public static void main(String[] args) {Looper.prepareMainLooper(); //创建sMainLooperLooper.loop();} } prepareMainLooper创建了sMainLooper public final class Looper {priva…

Redis常见面试问题

1、Redis分布式锁是怎么实现的 Redis实现分布式锁的7种方案-CSDN博客 2、Redis分布式锁会有什么问题 Redis实现分布式锁的7种方案-CSDN博客 3、Redis有哪些操作时间复杂度不是O(1)&#xff1f; Redis有一些操作的时间复杂度不是O(1)&#xff0c;例如&#xff0c;删除List、…

nodejs微信小程序+python+PHP-婚纱摄影预约系统的设计与实现-安卓-计算机毕业设计

目 录 摘 要 I ABSTRACT II 目 录 II 第1章 绪论 1 1.1背景及意义 1 1.2 国内外研究概况 1 1.3 研究的内容 1 第2章 相关技术 3 2.1 nodejs简介 4 2.2 express框架介绍 6 2.4 MySQL数据库 4 第3章 系统分析 5 3.1 需求分析 5 3.2 系统可行性分析 5 3.2.1技术可行性&#xff1a;…

Linuxfork,写时拷贝

1.prinf隐藏的缓冲区 1.思考:为什么会有缓冲区的存在? 2.演示及思考? 1).演示缓存区没有存在感 那为什么我们感觉不到缓冲区的存在呢?我们要打印东西直接就打印了呢? 我们用代码演示一下: 比如打开一个main.c,输入内容如下: #include <stdio.h> int main() { …

LLaMA 2:开源的预训练和微调语言模型推理引擎 | 开源日报 No.86

facebookresearch/llama Stars: 36.0k License: NOASSERTION LLaMA 2 是一个开源项目&#xff0c;用于加载 LLaMA 模型并进行推理。 该项目的主要功能是提供预训练和微调后的 LLaMA 语言模型的权重和起始代码。这些模型参数范围从 7B 到 70B 不等。 以下是该项目的关键特性…

kubernetes架构及核心组件简单介绍

目录 整体架构控制面kube-apiserver访问控制通知 kube-scheduler概述默认调度策略 kube-controller-manageretcd架构Raft协议日志复制 数据面kubeletkube-proxy 整体架构 集群架构图 控制面 控制面是kubernetes的核心组件&#xff0c;负责管理和控制集群的整体行为&#xf…

Python文件路径常用操作

1 文件路径 在进行数据处理时&#xff0c;经常要用代码去读文件里的数据&#xff0c;那么首先就得知道这个文件的文件路径。文件路径简单地说就是文件的存放位置。文件路径分为两块&#xff1a;文件夹路径和文件名&#xff0c;文件名又分为文件基本名和扩展名。 举例说明&…

解析MySQL Binlog:从零开始的入门指南【binlog入门指南】

&#x1f38f;&#xff1a;你只管努力&#xff0c;剩下的交给时间 &#x1f3e0; &#xff1a;小破站 解析MySQL Binlog&#xff1a;从零开始的入门指南 前言第一&#xff1a;MySQL中的Binlog详解1. 什么是Binlog&#xff1f;2. Binlog的作用a. 数据恢复&#xff1a;b. 主从复制…

jdk17安装全方位手把手安装教程 / 已有jdk8了,安装JDK17后如何配置环境变量 / 多个不同版本的JDK,如何配置环境变量?

&#x1f9f8;欢迎来到dream_ready的博客&#xff0c;&#x1f4dc;相信您对博主首页也很感兴趣o (ˉ▽ˉ&#xff1b;) 学生邮箱白嫖/免费安装JetBrains全家桶(IDEA/pycharm等) —— 保姆级教程 目录 1、下载jdk17 2、安装jdk17 3、配置环境变量 -> 电脑无其他jdk 4、…

【Git】修改提交信息(单次、批量)

文章目录 修改最近一次 commit 的提交信息修改某次 commit 的提交信息方法总结 修改最近一次 commit 的提交信息 git commit --amend -m "new message"修改某次 commit 的提交信息 git log --oneline 运行结果如下2f80f1b commit 4 9ee990a 第三次提交 40f2f03 comm…

Echarts title标题配置项的使用 更改颜色 副标题

title配置项主要是对图表的标题进行配置 title配置项所有属性文档 title&#xff1a; { ...... }设置标题 副标题 text: 简单创建柱形图,//图表标题 subtext: 副标题,如果想对副标题设置 超链接 边框 颜色 宽度…等 比如&#xff1a;设置超链接 sublink:‘…’, 设置标题位置…

【Unity入门】LayerMask小结

LayerMask常用的几种方法 LayerMask.GetMask 根据 层名称 获取其层遮罩值 遮罩值 2^层索引 假设 UserLayerA 和 UserLayerB 是第十层和第十一层。 这两个层会具有 User Layer 值 10 和 11。若要获取其层遮罩值&#xff0c; 可以将其名称传入 GetMask。参数可以是 其名称的列…

10_7iic整体框架流程

在内核中 这边把iic整个流程分成了 4层 iic_dtiver at24_iic_eeprom 也就是我们的自己的驱动 i2c-core.c 核心层 i2c/busses/i2c-s3c2410.c 控制器层 平台总线驱动层,或者也是图中的设备树 硬件描述 我们假设 板子上有三个iic控制器 0 1 2 这里在控制器0 上挂载了gt24c02的eep…

STK Components 二次开发-创建地面站

1.地面站只需要知道地面站的经纬高。 // Define the location of the facility using cartographic coordinates.var location new Cartographic(Trig.DegreesToRadians(-75.596766667), Trig.DegreesToRadians(40.0388333333), 0.0); 2.创建地面站 创建方式和卫星一样生成对…

Centos 7 离线安装(tar) NodeJS 16 和 Vue

目录 一、下载Nodejs二、安装Nodejs2.1、创建安装目录2.2、上传安装包(无网络) or 直接下载(有网络)2.3、解压缩2.4、配置环境变量2.5、创建软连接2.6、更换镜像源2.7、验证是否安装成功 三、安装Vue 一、下载Nodejs Nodejs&#xff1a;https://nodejs.org/en/ 历史版本&#…

如何使用JMeter测试导入接口/导出接口

今天一上班&#xff0c;被开发问了一个问题&#xff1a;JMeter调试接口&#xff0c;文件导入接口怎么老是不通&#xff1f;还有导出文件接口&#xff0c;不知道文件导到哪里去了&#xff1f; 我一听&#xff0c;这不是JMeter做接口测试经常遇到的嘛&#xff0c;但是一时半会又…

解决视口动画插件jquery.aniview.js使用animate.css时无效的问题(最新版本网页视口动画插件的使用及没作用、没反应)

当网站页面元素进入视口时自动应用过渡效果。CSS过渡效果可以为网页添加动画效果&#xff0c;并提供了一种平滑的转换方式&#xff0c;使元素的变化更加流畅和生动。而通过jQuery插件来获取页面滚动位置决定合适调用动画效果。 一、官网 animate.css官网 一款强大的预设css3动…

Linux的基本指令(四)

目录 前言 时间相关的指令 date指令 时间戳 日志 时间戳转化为具体的时间 cal指令 find指令&#xff08;十分重要&#xff09; grep指令&#xff08;行文本过滤工具&#xff09; 学前补充 什么是打包和压缩&#xff1f; 为什么要打包和压缩&#xff1f; 怎么打包和…

【数据结构】二叉树oj题

在处理oj题之前我们需要先处理一下之前遗留的问题 在二叉树中寻找为x的节点 BTNode* BinaryTreeFind(BTNode* root, int x) {if (root NULL)return NULL;if (root->data x)return root;BTNode* ret1 BinaryTreeFind(root->left, x);BTNode* ret2 BinaryTreeFind(ro…