如何理解:进程控制
文章目录
- 前言:
- 进程创建:
- 进程终止:
- 如何终止进程?
- 进程等待
- 非阻塞等待:
- 总结:
前言:
对于前面的地址空间的学习,我们现在了解到原来所谓变量的地址其实是虚拟地址,该虚拟地址是通过页表的映射关系从而找到内存中真实的物理地址!下面我们进入到关于进程的控制
进程创建:
现在我们对进程就有了新的定义:进程 = 内核的相关管理数据数据结构(task_struct + mm_struct + 页表) + 代码和数据。 其中代码是父子进程共享的,数据是判断是否发生写实拷贝的。
-
fork函数的返回值
我们之前也有过介绍,fork函数是用来创建子进程的,创建子进程成功则返回0,对于父进程的返回值则是子进程的pid,这一点虽然我上次没有细讲,但是在截图时就会发现。可以看看我之前博客——>进程理解
- 子进程返回0
- 父进程返回子进程的pid
-
为什么父进程返回的是子进程的pid,给子进程返回的却是0呢?
要记住,我们在讲解进程状态的时候,对于僵尸进程我们有过介绍,父进程是会管理子进程的,谈到管理永远是6个字——>”先描述,再组织“。
所以当然是为了让父进程方便对子进程进行标识,进而进行管理!
-
fork函数的常规用法
- 一个父进程希望复制自己,使得父子进程同时执行不同的代码段。例如,父进程等待客户端请求,生成子进程来处理请求。
- 一个进程要执行一个不同的程序。例如子进程从fork返回后,调用exec函数。
-
fork调用失败的原因
- 系统中有太多的进程。
- 实际用户创建数超过限制。
进程终止:
-
进程终止是在做什么呢?
首先我们应该清楚一件事情——>当加载进程是,应当是先创建PCB、页表和地址空间等,再是加载代码和数据。
所以进程终止是在:
- 释放曾经的代码和数据所占据的空间。
- 释放内核数据结构(若task_struct受僵尸状态,则演示释放)
-
进程终止的3种情况
- 提出问题:为什么main函数最后要返回0呢?为什么不是1或者100呢?
#include <stdio.h> #include <unistd.h> #include <sys/types.h>int main() {printf("I am a process! pid: %d, ppid: %d\n", getpid(), getppid());sleep(1);return 100; }
[!TIP]
我们可以使用指令:echo $?
该指令代表父进程bash获取到子进程的退出码,0表示成功,非0表示失败。退出码的意义就是告诉关心方(父进程),我把任务处理的怎么了。
但我要是再次执行echo $?指令,表示的就是刚刚子进程的退出码,毕竟该指令是获取最近进程的退出码。
那这个退出码究竟有什么用,你想返回100或者0不是都可以吗。诶,这个时候操作系统会给我们提供个新的系统调用函数:strerror函数用来获取系统错误信息或打印用户程序错误信息,下面我们来用用这个系统调用
[!TIP]
#include <stdio.h> #include <unistd.h> #include <sys/types.h> #include <string.h>int main() {int errorcode = 0;for(errorcode = 0; errorcode <= 255; ++errorcode){printf("%d -> %s\n", errorcode, strerror(errorcode));}return 100; }
我们会打印特别多的错误码,每个错误码都对应了一个描述。所以为什么我们最后都会返回0,就是因为如果全部代码都执行完毕后,那肯定是不会存在问题的,那么就返回0。如果其中有一处有错误,系统就会直接进行返回相应的错误码。
这里是我随便ls一个文件夹,因为在当前目录未找到该文件夹,所以执行该指令的进程就会向bash返回相对应的错误码2。
所以我们在输入指令的时候,本质也是OS创建子进程然后子进程在执行。这点我们通过指令echo $?可以很好的证明。
这也能很好地说明bash为什么要得到子进程的退出码,为了知道子进程退出的情况(是否成功,失败又是什么原因),当然bash只是提供信息,不会自动解决,这只是一种为用户负责的体现
-
我们也可以实现自定义退出码
#include <stdio.h> #include <unistd.h> #include <sys/types.h> #include <string.h>enum {ERROR_DIV = -1,NOMARL_DIV };int exit_code = NOMARL_DIV;int Div(int a, int b) {if(b == 0){exit_code = ERROR_DIV;//printf("This is error!\n");return exit_code;}exit_code = NOMARL_DIV;return a/b; }const char* ErrorMode(int mode) {switch(mode){case ERROR_DIV:return "Div zero";case NOMARL_DIV:return "Div nomarl";default:return "Unknow error!";} }int main() {int ans = Div(4, 0);printf("ans is-> %d[%s]\n", ans, ErrorMode(exit_code));ans = Div(4, 2);printf("ans is-> %d[%s]\n", ans, ErrorMode(exit_code));return 0; }
通过判断分母是否为0,从而实现判断结果是否是正确的!
-
退出信号
int main() {int* p = NULL;*p = 3;printf("%d\n", *p);return 0; }
对于上述代码,我们执行起来百分之百会出现报错,如果我们在VS上也写过这样的bug,那么该错误就是一个很典型的——段错误
这个时候代码不会跑完,因为在执行的过程中出现了异常,就提前退出了。就像在VS写代码时,代码崩溃了,此时OS发现你的进程做了不该做的事情,OS就会杀掉进程,一旦出现了异常,退出码就没意义了。
比如此时我使用指令echo $? 打印退出码,是会发现退出码为139,但是退出码在133后就是未知了:
-
为什么会出现异常呢?
本质上是因为进程收到了OS发给进程的信号。
我们可以使用指令:kill -l
段错误的出现就是对应的11号信号
所以衡量一个进程的退出,我们(父进程bash)只需要两个数字:退出码 + 退出信号
第一步是先确认是否异常
若不是异常就一定是代码跑完了,看退出码就好了。
-
-
[!IMPORTANT]
所以进程终止的3种情况:
- 代码跑完,结果正确
- 代码跑完,结果不正确
- 代码执行时,出现了异常,提前退出了
如何终止进程?
- main函数的return,表示进程终止(非main函数的return,代表函数结束)
- 代码调用exit函数(在代码任意位置调用exit函数都表示进程退出)
- 通过系统调用函数_exit( )
-
exit( ) VS _exit( )
-
exit( )是库函数,_exit( )是系统调用
-
exit函数会在进程退出的时候,重置缓冲区,而 _exit( )不会,因此我们可以猜测缓冲区是在exit那一层,也可以说明使用exit( )库函数的本质是在调用系统调用函数 _exit( ).
#include <stdio.h> #include <stdlib.h>int main() { printf("Hello, this is a test code!");exit(0); }
#include <stdio.h> #include <stdlib.h>int main() { printf("Hello, this is a test code!");_exit(0); }
-
进程等待
任何进程,在退出的情况系,一般必须要被父进程进行等待!
-
为什么父进程要等待?
- 进程在退出的时候,如果父进程不管不顾,退出进程会出现僵尸状态从而到时内存泄漏。所以父进程通过等待,解决子进程出现的僵尸问题,为了回收系统资源。(这是一定要考虑的)
- 获取子进程的退出信息,知道子进程是因为什么退出的。(可选的功能)
-
该怎么进行等待?
我们可以使用系统调用函数:wait( ) 和 waitpid( ) 函数
#include <sys/types.h>
#include <sys/wait.h>pid_t wait(int* status);
pid_t waitpid(pid_t pid, int* status, int options);-
wait( )
- 父进程阻塞等待任意一个子进程,子进程不退则父进程不退。
- 该函数能够回收子进程资源,以及获取子进程的 pid。
返回值:成功返回被等待进程的pid,失败返回-1.
参数:输出型参数,获取子进程退出状态,不关心则可以设置成NULL#include <stdio.h> #include <unistd.h> #include <sys/types.h> #include <stdlib.h> #include <sys/wait.h>void ChildRun() {int cnt = 1;while(cnt <= 5){printf("%d-> I am child process, pid: %d, ppid: %d\n", cnt, getpid(), getppid());sleep(1);cnt++;} }int main() {printf("I am process!\n");pid_t id = fork();if(id == 0){// childChildRun();printf("Child quit...\n");exit(123);}// fatherpid_t rid = wait(NULL);if(rid > 0){printf("wait success, rid: %d\n", rid);}else{printf("wait failed!\n");}sleep(2);return 0; }
最后是等待成功并且返回了子进程的pid
-
waitpid( )
回收子进程资源,解决僵尸问题的同时,还能够获取子进程退出信息。
返回值:
- pid_t > 0:等待成功,子进程退出,并且父进程回收成功
- pid_t < 0:等待失败
- pid_t == 0:检测是成功的,只不过子进程还没退出,需要你下一次重复等待。
参数:
-
pid_t pid
- pid == -1,等待任意一个子进程,与wait一样。
- pid > 0,等待其进程的ID与pid相等的子进程。
-
int* status
- 输出型参数,用来保存退出信息,保存退出码 + 退出信号,让我们知道“退出”的情况如何
-
int options
-
指定父进程的等待方式,为 0 则让父进程进行 阻塞 等待,非 0 则进行 非阻塞 等待。
#include <stdio.h> #include <unistd.h> #include <sys/types.h> #include <stdlib.h> #include <sys/wait.h> void ChildRun() {int cnt = 1;while(cnt <= 5){printf("%d-> I am child process, pid: %d, ppid: %d\n", cnt, getpid(), getppid());sleep(1);cnt++;} }int main() {printf("I am process!\n");pid_t id = fork();if(id == 0){// childChildRun();printf("Child quit...\n");exit(123);}// fatherint status = 0;pid_t rid = waitpid(-1, &status, 0);if(rid > 0){printf("wait success, rid: %d\n", rid);}else{printf("wait failed!\n");}sleep(2);printf("father process quit, status: %d\n", status);return 0; }
最后的运行结果status是31488,这么奇怪的数字。
-
分析status
我们介绍过,status是输出型参数,记录了该进程的退出码 + 退出信号,但是我们应该如何利用一个值记录退出码和退出信号呢?
[!NOTE]
我们都知道,一个int* 变量的大小是4字节,那么也就是32个比特位,但status不能简单的当做int 来看待,可以当做位图来看,我们只研究低16比特位*
分为两种情况:
- 正常终止时:高8位为退出码信息,低8位默认全部为0
- 被信号杀掉时(出现异常):高8位不再使用,低7位存储终止信号,中间还有一位存core dump标志位
红色各段就代表着 退出码 + 退出信号,退出码拥有8个比特位,而终止信号拥有7个比特位
所以我们上述的31488其实是两个数据的整合,但是我们也可以打印出来看看。
我们需要将退出码先挪动到低8位,再转换为10进制,所以我们可以用位运算操作符:
打印退出码:(status >> 8) & 0xFF
打印退出信号:status & 0x7F
printf("exit_code: %d, exit_signal: %d\n", (status >> 8)&0xFF, (status & 0x7F));
最后的输出结果:
因为我的子进程最后exit了123,又因为这是正常终止,所以返回退出码接收到了123.
但如果我加一个段错误,再运行就会如下图所示:
此时是异常退出,status就会收到退出信号而非退出码。
我们也可以利用两个宏,来查出退出码:
- WIFEXITED(status):若位正常终止子进程返回的状态,则为真。(主要是查看子进程是否正常退出,本质上是查看signal位是否满足。
- WEXITSTATUS(status):若WIFEXITED非空,提取子进程的退出码。
if(WIFEXITED(status)) {printf("child process quit success, chid exit code: %d\n", WEXITSTATUS(status)); } else {printf("child process quit unnormal!\n"); }
-
-
-
非阻塞等待:
我们上面讲的等待过程是属于阻塞等待,还记得我们在进程状态部分讲解过阻塞态吗,还记得阻塞态是会进入等待队列的吗?如果你有疑问的话可以去看看我之前写的博客:进程的祖册、挂起和运行状态。
那我们刚刚介绍waitpid( )系统调用时,也介绍了参数option是控制关于非阻塞等待的,如果参数option为0,那就是阻塞等待,父进程会等待子进程结束,再进行父进程的操作,这是阻塞等待。
非阻塞等待是父进程在等待子进程的过程中,父进程同时也在做某些操作,这就是非阻塞等待。
[!NOTE]
我们可以使用宏:WNOHANG 来表示父进程进入非阻塞等待。
#include <stdio.h> #include <unistd.h> #include <sys/types.h> #include <stdlib.h> #include <sys/wait.h>void ChildRun() {int cnt = 1;while(cnt <= 5){printf("%d-> I am child process, pid: %d, ppid: %d\n", cnt, getpid(), getppid());sleep(1);cnt++;} }void DoOtherThing() {printf(" I am doing my father things while child process is running!\n"); }int main() {printf("I am a process, pid: %d, ppid: %d\n", getpid(), getppid());pid_t id = fork();if(id == 0){//childChildRun();printf("child process quit...\n");sleep(1);exit(0);}//fatherint status = 0;while(1){pid_t rid = waitpid(0, &status, WNOHANG);if(rid == 0){printf(" Just checking child process...\n");DoOtherThing();sleep(1);}else if(rid > 0){printf("wait suceess!\n");break;}else{printf("wait failed!\n");break;}}if(WIFEXITED(status)){sleep(1);printf("father process quit success!\n");printf("exit_code: %d, exit_signal: %d\n", WEXITSTATUS(status), status&0x7F);}else{printf("quit unnormal!\n");}return 0; }
通过以上代码,我们就能实现子进程运行的同时,父进程也在运行自己的任务:
总结:
现在我们已经学会了有关进程创建的话题,接下来我们将要讨论进程替换的话题。
相关文章:

如何理解:进程控制
文章目录 前言:进程创建:进程终止:如何终止进程?进程等待非阻塞等待: 总结: 前言: 对于前面的地址空间的学习,我们现在了解到原来所谓变量的地址其实是虚拟地址,该虚…...

工业互联网边缘计算实训室解决方案
一、引言 随着物联网(IoT)、5G通信技术的快速发展,工业互联网已成为推动制造业转型升级的重要力量。边缘计算作为云计算的延伸和补充,在实时数据分析、降低数据传输延迟、提升处理效率及增强数据安全性方面展现出巨大潜力。在此背…...

Android全面解析之Context机制(一) :初识Android context
什么是Context 回想一下最初学习Android开发的时候,第一用到context是什么时候?如果你跟我一样是通过郭霖的《第一行代码》来入门android,那么一般是Toast。Toast的常规用法是: Toast.makeText(this, "我是toast", To…...

气象百科——气象监测站的介绍
气象监测站是专门用于监测和记录大气环境状态及变化规律的设施。这些站点通过安装各种观测仪器,如温度传感器、湿度传感器、气压传感器、风速风向传感器、雨量传感器以及近年来兴起的雷达水位计等,全方位、多角度地收集大气中的温度、湿度、气压、风速风…...
学懂C++(三十):高级教程——深入解析 C++ Windows API 的多线程支持
引言 在现代应用程序中,多线程编程是实现高性能和高并发任务的关键手段。Windows 操作系统为开发者提供了一套强大的 API,用于创建和管理线程、同步任务,并优化线程性能。本文将深入探讨 C 中 Windows API 的多线程支持,详细介绍线…...

苹果笔记本电脑可以玩steam游戏吗 MacBook支持玩steam游戏吗 在Steam上玩黑神话悟空3A大作 苹果Mac怎么下载steam
游戏是生活的润滑剂,越来越多的用户开始关注Mac平台上可玩的游戏。幸运的是,Steam作为最大的数字发行平台之一,提供了大量适用于Mac操作系统的游戏。无论你是喜欢策略、冒险还是射击类游戏,都能在Steam上找到适合自己Mac设备玩耍的…...

海康摄像头(测温型)桌面客户端开发分享
分享一个自己开发的用于企业特殊场景下温度监控告警的一个桌面应用。 关键功能: 1.支持海康摄像头; 2.支持多路视频预览,多通道画面选择预览,支持视频画面回放与下载; 3.支持自动探测摄像头功能,若具备…...

骑行耳机哪个品牌性价比高?精选五大畅销骑行耳机推荐!
骨传导耳机凭借不入耳佩戴更舒适、健康等特定在短时间内迅速风靡骑行圈,其独特的设计不仅让骑行者在享受音乐的同时保持对周围环境的警觉,还因其非入耳式的佩戴方式,有效避免了长时间骑行对耳朵的压迫与不适。它不仅能够激发骑行时的激情与动…...

libcurl8.9.1 上传json
在postman中 PUT----》body----》raw----》json 结构体定义: #define MAX_ARRAY_SIZE 5*1024*1024struct SMART_DATA_CACHE {char* buf;long dwTotalLen;SMART_DATA_CACHE(){dwTotalLen 0;buf nullptr;while (!buf) {try {buf new char[MAX_ARRAY_SIZE];}c…...

什么是暗水印?企业暗水印如何实施?企业保护利器
“明察秋毫之末,而不见舆薪。” 此言道出了观察之细致入微,却也隐含了信息泄露之隐忧。 在今日之数字时代,信息如潮水般汹涌,而电脑屏幕作为信息展示的重要窗口,其安全性亦成为众人关注的焦点。 当谈及监控电脑屏幕以…...

Qt 系统相关 - 文件
目录 1. 文件概述 2. 输入输出设备类 3. 文件读写类 4. 文件和目录信息类 1. 文件概述 文件操作是应用程序必不可少的部分。Qt 作为一个通用开发库,提供了跨平台的文件操作能力。 Qt 提供了很多关于文件的类,通过这些类能够对文件系统进行操作&#x…...
Android Toast居中显示方法二
Android Toast居中显示方法一请看 Android Toast设置居中显示方法一-CSDN博客 下面来讲讲第二种方法: Toast toast Toast.makeText(MainActivity.this, "my toast", Toast.LENGTH_SHORT);LinearLayout.LayoutParams layoutParams new LinearLayout.Lay…...

Vue启动时报异常 ‘error:03000086:digital envelope routines::initialization error‘
问题描述 启动Vue项目时,突发报如下异常: opensslErrorStack: [error:03000086:digital envelope routines::initialization error,error:0308010C:digital envelope routines::unsupported],library: digital envelope routines,reason: unsupported,…...
C#委托—马工教你轻松玩转委托
前言 在C#中有一个小白谈之色变的知识点叫委托,很多学了一两年C#的还不知道委托究竟是什么东西,本文就来帮你彻底解开解惑,从此委托就是小儿科! 1、委托的本质 委托也是一种类型,大家知道所有的类型一定对应一种数据…...

当下最强的 AI art 生成模型 Stable Diffusion 最全面介绍
目录 模型生成效果展示(prompt 全公开) 如何注册 Stable Diffusion 使用 SD(dreamstudio.ai )的收费标注如何 SD 提供哪些参数可以设置 如何使用种子来改进一张作品 我用 SD 创作的图片著作权如何归属,可以拿来商…...
NPM 使用教程:从入门到精通
NPM 使用教程:从入门到精通 1. 引言 什么是 NPM? NPM (Node Package Manager) 是 JavaScript 的包管理工具,也是世界上最大的开源库生态系统。它帮助开发者轻松地管理项目的依赖、安装和分享包。NPM 与 Node.js 紧密结合,并在开…...

基于ssm+vue+uniapp的停车场小程序的设计与实现
开发语言:Java框架:ssmuniappJDK版本:JDK1.8服务器:tomcat7数据库:mysql 5.7(一定要5.7版本)数据库工具:Navicat11开发软件:eclipse/myeclipse/ideaMaven包:M…...

C语言典型例题37
《C程序设计教程(第四版)——谭浩强》 例题3.5 按照按照考试成绩的等级输出百分制分数段,A等为85分以上,B等为70~84分,C等为 60~69分,D等在60分以下,成绩的等级从键盘输入 代码: //…...

二自由度机械臂软件系统(三)ros2_control硬件底层插件
ros2_control实现了两个功能,一个是控制算法插件即控制的实现,另一个是底层插件即跟硬件通信的功能。 参考资料:https://zhuanlan.zhihu.com/p/682574842 1、创建功能包 ros2 pkg create --build-type ament_cmake robot_control_test在sr…...

24.8.9.11数据结构|链栈和队列
链栈 1、理解 实际上是一个仅在表头进行操作的单链表,头指针指向栈顶结点或头结点,以下恋栈均指带头结点的链栈. 2、 基本操作 1、定义结构:节点含有数据域和指针域 2、初始化操作:建立一个带头结点的空栈 3、取栈顶元素操作:取出栈的栈顶元…...

【论文阅读28】-CNN-BiLSTM-Attention-(2024)
本文把滑坡位移序列拆开、筛优质因子,再用 CNN-BiLSTM-Attention 来动态预测每个子序列,最后重构出总位移,预测效果超越传统模型。 文章目录 1 引言2 方法2.1 位移时间序列加性模型2.2 变分模态分解 (VMD) 具体步骤2.3.1 样本熵(S…...
全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比
目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec? IPsec VPN 5.1 IPsec传输模式(Transport Mode) 5.2 IPsec隧道模式(Tunne…...
Web 架构之 CDN 加速原理与落地实践
文章目录 一、思维导图二、正文内容(一)CDN 基础概念1. 定义2. 组成部分 (二)CDN 加速原理1. 请求路由2. 内容缓存3. 内容更新 (三)CDN 落地实践1. 选择 CDN 服务商2. 配置 CDN3. 集成到 Web 架构 …...

C# 求圆面积的程序(Program to find area of a circle)
给定半径r,求圆的面积。圆的面积应精确到小数点后5位。 例子: 输入:r 5 输出:78.53982 解释:由于面积 PI * r * r 3.14159265358979323846 * 5 * 5 78.53982,因为我们只保留小数点后 5 位数字。 输…...

RSS 2025|从说明书学习复杂机器人操作任务:NUS邵林团队提出全新机器人装配技能学习框架Manual2Skill
视觉语言模型(Vision-Language Models, VLMs),为真实环境中的机器人操作任务提供了极具潜力的解决方案。 尽管 VLMs 取得了显著进展,机器人仍难以胜任复杂的长时程任务(如家具装配),主要受限于人…...

基于Springboot+Vue的办公管理系统
角色: 管理员、员工 技术: 后端: SpringBoot, Vue2, MySQL, Mybatis-Plus 前端: Vue2, Element-UI, Axios, Echarts, Vue-Router 核心功能: 该办公管理系统是一个综合性的企业内部管理平台,旨在提升企业运营效率和员工管理水…...
Python Einops库:深度学习中的张量操作革命
Einops(爱因斯坦操作库)就像给张量操作戴上了一副"语义眼镜"——让你用人类能理解的方式告诉计算机如何操作多维数组。这个基于爱因斯坦求和约定的库,用类似自然语言的表达式替代了晦涩的API调用,彻底改变了深度学习工程…...

Golang——9、反射和文件操作
反射和文件操作 1、反射1.1、reflect.TypeOf()获取任意值的类型对象1.2、reflect.ValueOf()1.3、结构体反射 2、文件操作2.1、os.Open()打开文件2.2、方式一:使用Read()读取文件2.3、方式二:bufio读取文件2.4、方式三:os.ReadFile读取2.5、写…...
安卓基础(Java 和 Gradle 版本)
1. 设置项目的 JDK 版本 方法1:通过 Project Structure File → Project Structure... (或按 CtrlAltShiftS) 左侧选择 SDK Location 在 Gradle Settings 部分,设置 Gradle JDK 方法2:通过 Settings File → Settings... (或 CtrlAltS)…...
Qt 事件处理中 return 的深入解析
Qt 事件处理中 return 的深入解析 在 Qt 事件处理中,return 语句的使用是另一个关键概念,它与 event->accept()/event->ignore() 密切相关但作用不同。让我们详细分析一下它们之间的关系和工作原理。 核心区别:不同层级的事件处理 方…...