如何理解:进程控制
文章目录
- 前言:
- 进程创建:
- 进程终止:
- 如何终止进程?
- 进程等待
- 非阻塞等待:
 
- 总结:
前言:
 对于前面的地址空间的学习,我们现在了解到原来所谓变量的地址其实是虚拟地址,该虚拟地址是通过页表的映射关系从而找到内存中真实的物理地址!下面我们进入到关于进程的控制
进程创建:
 现在我们对进程就有了新的定义:进程 = 内核的相关管理数据数据结构(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、取栈顶元素操作:取出栈的栈顶元…...
 
eNSP-Cloud(实现本地电脑与eNSP内设备之间通信)
说明: 想象一下,你正在用eNSP搭建一个虚拟的网络世界,里面有虚拟的路由器、交换机、电脑(PC)等等。这些设备都在你的电脑里面“运行”,它们之间可以互相通信,就像一个封闭的小王国。 但是&#…...
web vue 项目 Docker化部署
Web 项目 Docker 化部署详细教程 目录 Web 项目 Docker 化部署概述Dockerfile 详解 构建阶段生产阶段 构建和运行 Docker 镜像 1. Web 项目 Docker 化部署概述 Docker 化部署的主要步骤分为以下几个阶段: 构建阶段(Build Stage):…...
在HarmonyOS ArkTS ArkUI-X 5.0及以上版本中,手势开发全攻略:
在 HarmonyOS 应用开发中,手势交互是连接用户与设备的核心纽带。ArkTS 框架提供了丰富的手势处理能力,既支持点击、长按、拖拽等基础单一手势的精细控制,也能通过多种绑定策略解决父子组件的手势竞争问题。本文将结合官方开发文档,…...
STM32+rt-thread判断是否联网
一、根据NETDEV_FLAG_INTERNET_UP位判断 static bool is_conncected(void) {struct netdev *dev RT_NULL;dev netdev_get_first_by_flags(NETDEV_FLAG_INTERNET_UP);if (dev RT_NULL){printf("wait netdev internet up...");return false;}else{printf("loc…...
 
(二)TensorRT-LLM | 模型导出(v0.20.0rc3)
0. 概述 上一节 对安装和使用有个基本介绍。根据这个 issue 的描述,后续 TensorRT-LLM 团队可能更专注于更新和维护 pytorch backend。但 tensorrt backend 作为先前一直开发的工作,其中包含了大量可以学习的地方。本文主要看看它导出模型的部分&#x…...
 
STM32标准库-DMA直接存储器存取
文章目录 一、DMA1.1简介1.2存储器映像1.3DMA框图1.4DMA基本结构1.5DMA请求1.6数据宽度与对齐1.7数据转运DMA1.8ADC扫描模式DMA 二、数据转运DMA2.1接线图2.2代码2.3相关API 一、DMA 1.1简介 DMA(Direct Memory Access)直接存储器存取 DMA可以提供外设…...
基础测试工具使用经验
背景 vtune,perf, nsight system等基础测试工具,都是用过的,但是没有记录,都逐渐忘了。所以写这篇博客总结记录一下,只要以后发现新的用法,就记得来编辑补充一下 perf 比较基础的用法: 先改这…...
python爬虫:Newspaper3k 的详细使用(好用的新闻网站文章抓取和解析的Python库)
更多内容请见: 爬虫和逆向教程-专栏介绍和目录 文章目录 一、Newspaper3k 概述1.1 Newspaper3k 介绍1.2 主要功能1.3 典型应用场景1.4 安装二、基本用法2.2 提取单篇文章的内容2.2 处理多篇文档三、高级选项3.1 自定义配置3.2 分析文章情感四、实战案例4.1 构建新闻摘要聚合器…...
 
WordPress插件:AI多语言写作与智能配图、免费AI模型、SEO文章生成
厌倦手动写WordPress文章?AI自动生成,效率提升10倍! 支持多语言、自动配图、定时发布,让内容创作更轻松! AI内容生成 → 不想每天写文章?AI一键生成高质量内容!多语言支持 → 跨境电商必备&am…...
 
Android 之 kotlin 语言学习笔记三(Kotlin-Java 互操作)
参考官方文档:https://developer.android.google.cn/kotlin/interop?hlzh-cn 一、Java(供 Kotlin 使用) 1、不得使用硬关键字 不要使用 Kotlin 的任何硬关键字作为方法的名称 或字段。允许使用 Kotlin 的软关键字、修饰符关键字和特殊标识…...
