当前位置: 首页 > news >正文

Linux:进程的创建、终止和等待

一、进程创建

1.1 fork函数初识

#include pid_t fork(void);

返回值:子进程中返回0,父进程返回子进程id,出错返回-1

 调用fork函数后,内核做了下面的工作:

1、创建了一个子进程的PCB结构体、并拷贝一份相同的进程地址空间和页表(PCB结构体中的一个指针指向该空间)

2、子进程和父进程起初共享代码和数据,并且页表中的虚拟地址和物理地址的映射关系是一样的,所以也指向相同的物理空间。    

3、fork返回后将子进程添加到系统的进程列表中,由调度器调用(每个进程开始自己的旅程)

4、一旦其中任意一方尝试修改数据,那么就会发生写时拷贝,会开辟一块新的物理内存,然后改变页表的映射关系。

 1.2 写时拷贝

       通常,父子代码共享,父子再不写入时,数据也是共享的,当任意一方试图写入,便以写时拷贝的方式各自一份副本。 

1.3 fork函数存在的意义

fork函数常规用法:

1、一个父进程希望复制自己,使父子进程同时执行不同的代码段。例如,父进程等待客户端请求,生成子进程来处理请求。

2、一个进程要执行一个不同的程序。例如子进程从fork返回后,调用exec函数。 (进程替换)

 1.4 fork调用失败的原因

1、系统中有太多的进程

2、实际用户的进程数超过了限制

二、进程终止

 问题引入:为什么main函数要返回0?返回多少的意义是什么???

——>成功只有一种情况,但是失败可以有无数的原因和理由!! 所以main函数的本质是进程运行时是否是正确的结果,如果不是,可以用不同的数字表示不同的出错原因

进程退出场景:

1/代码运行完毕,结果正确

2/代码运行完毕,结果不正确

3/代码异常终止

2.1 运行完毕结果不正确

正常终止(可以通过 echo $? 查看进程退出码):       $?->保存最后一次进程退出的退出码

1. 从main返回

2. 调用exit

3. _exit

2.2.1 main函数返回

 进程中,谁会关心我的运行情况呢??——>父进程 !

       其实main函数本质上也是一个被别人调用的函数,所以他return的结果其实是想告诉他的父进程自己的运行情况。

2.2.2 退出码概念

父进程可以通过拿到子进程的退出码,不同的退出码分别代表的是不同的原因!!

问题1:为什么需要有退出码呢??遇到问题我直接printf输出一下错误原因,或者是直接看结果不就可以了吗??? 

——>没有人规定代码程序必须得打印!比如说该错误并不是从显示器打印而是向网络写入

问题2:错误码适合计算机去看,但是不适合人去看,所以我们是否可以将他转换成字符串的错误信息解读??

——> strerror函数,可以帮助我们将错误码信息转变为字符串的错误信息解读,这些退出码本质上就是错误码,由系统提供

我们可以将一些错误码对应的信息打印出来

 问题3:我们的退出码其实使用的是系统提供的错误码体系,那么我们可不可以自己造一个退出码体系呢??

——>该体系是C标准库提供的,但是我们写的代码一般不是纯C写的,所以一般会自己搞一个退出码体系 

 问题4:父进程为啥要关心子进程的运行状况呢??

——>父进程创建子进程的目的就是为了让子进程执行和自己不一样的代码流来完成某些特定的任务,父进程本身也就是一个跑腿的,因为代码是用户写的,所以真正关心的是用户,用户需要知道子进程将自己的工作完成得怎样了

问题5:全局变量erron

——>保存最后一次执行的错误码

       这样的写法既可以直接在进程返回前知道错误码,然后再变成错误信息打印出来,并且也可以在进程结束后让父进程知道运行的情况

2.2.3 库函数函数exit 

exit和return的区别:return和exit在main函数里是等价的,因为exit表示退出进程,而main函数恰好执行完return也会退出进程,但是return在其他函数中代表的是函数返回。

2.2.4 系统调用接口_exit

#include void _exit(int status);

参数:status 定义了进程的终止状态,父进程通过wait来获取该值

 _exit和exit的区别:一个是系统调用接口(更底层),一个是库函数,其实exit最后也会调用exit, 但在调用exit之前,还做了其他工作:

1. 执行用户通过 atexit或on_exit定义的清理函数。

2. 关闭所有打开的流,所有的缓存数据均被写入(缓存被清理了)

3. 调用_exit

所以exit比_exit多做了一层最重要的工作就是刷新缓存,我们还可以得出另一个结论就是:缓冲区绝对不在内核区!!——>因为如果在内核区的话,系统调用的_exit在终止的时候也必然会把缓冲区刷新一下,因为现代操作系统不做任何浪费时间和空间的事情,所以肯定不是由内核维护缓存区,而是由用户区在维护!!(_exit压根看不到缓冲区,所以这个工作只能有exit去完成)

2.2 异常中止 

      用退出码可以告诉父进程自己的执行情况,那如果是异常中止了呢??那就连运行完毕这个条件都完成不了,更别谈结果是否正确了,所以我们可以知道异常必然是最先需要被知道的!因为一旦异常了,一般代码都没跑完,即使跑完了,错误码也不能让人相信,此时退出码就没有意义了!

     举个例子:就好比我们平时考试一样,你考不好的时候大家会关心你为啥考不好,但如果你作弊了,性质就变了,即考得再好都让人觉得不可相信。

所以进程结束后应该优先判断该进程是否异常了,然后才能确定退出码能不能用!! 

 除0错误:

 野指针(段错误)

 类似除0、野指针这样的错误,会触发一些硬件级别的错误,比如除0,cpu的状态寄存器会出现溢出的错误,而野指针,也就是们即将访问的虚拟地址在页表中找不到对应的映射,或者是建立的映射关系只有只读权限,反正最终会转化成一些硬件级别的信号来给操作系统

所以,父进程需要关心子进程为什么异常,以及发生何种异常,系统会通过信号来告诉我们的进程发生了异常!! 

所以我们最关键的是要看父进程是否收到了信号,如果没有收到就没有异常(具体如何收到,就涉及到进程等待的知识)

三、 进程等待

3.1 如何理解

3.1.1 是什么

通过系统调用接口wait/waitpid,来对子进程进行状态检查和回收的功能!

3.1.2 为什么

1、解决内存泄漏的问题——>僵尸进程无法被杀死,必须通过进程等待来杀掉他。(必须完成)

2、通过进程等待获取子进程的退出情况——>知道我给子进程布置的任务完成得怎么样了——>可以关心也可以不关心(可以选择)

3.1.3 怎么做

父进程通过调用wait/waitpid方法来解决僵尸进程回收问题,以及获取子进程退出情况

3.2 wait和waitpid

#include pid_t wait(int*status);

返回值:成功返回被等待进程pid,失败返回-1

参数:输出型参数,获取子进程退出状态,不关心则可以设置成为NULL

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。

3.2.1 wait解读 

wait:(等待任意一个进程)

1、int *status :输出型参数  int会被当成几部分使用  不关心可设为NULL

问题1:父进程等待,我希望获取子进程的哪些信息呢??

——>(1)子进程的代码是否异常??(2)没有异常,结果对吗,不对的原因是什么?

 问题2:父进程为什么不定义全局变量的status,而必须用wait等系统调用来获取状态呢??

——>用全局变量的话,因为进程具有独立性!!所以子进程再怎么去改自己的status,父进程都看不到!(虽然表面上是一份代码),所以这个过程比如要通过系统调用接口来让操作系统帮助我们获取子进程的一些数据!!(因为OS不相信任何人) 

问题3:为什么int被分为好几个部分?? 

——>我们不仅需要知道是否发生异常,还需要知道退出状态,所以这个int需要拆分成bit位 

(1)低7为判断是否异常 status&0x7F 

(2)第8位core dump标志

(3)次8位判断退出原因  (status<<8)&0xFF

 2、返回值:成功返回被等待子进程pid,失败返回-1。

3.2.2 阻塞和非阻塞轮询

       如果子进程一直不退出,父进程默认在wait的时候,调用这个系统调用的时候,也就不返回,默认叫做阻塞状态 ——>通过这个我们可以知道阻塞不仅仅只是发生在向硬件发送请求时等待他的状态准备好,还可以发生在父进程在等待子进程结束从而获取他的状态。

      如何理解非阻塞轮询呢??我们来讲个小故事:

  1、你还有3天就要C语言考试了,但是你不以为然于是先玩了2他,当第3天的时候你慌了因为你平时上课没听而且啥也不懂,所以你找了一个班里的努力型学霸小张(喜欢学习并且做了很多笔记) 于是你走到楼下 但是你又懒得上去,于是你就打电话给小张“你能不能跟我去图书馆帮我复习几个小时,顺便教教我把笔记借我看看呗” 小张说:“好,但是我现在笔记还有几页没看完,你再楼下等等我,我等会就下去……” 然后你就把电话挂了,然后过了5分钟,你发现小张还没下来,然后你又打了电话,但是小张还是说等会就下去,就这样你打了十多个电话,终于小张下来了,于是你们开开心心地去往图书馆了……

        在这个过程中,你就是用户,你打电话的过程就是调用系统调用的过程,而小张就是操作系统,当你打电话询问小张的这个过程其实就是想操作操作系统询问:“你当前的状态准备好了没有?(检查状态)”   小张说等会就下来,于是你挂电话  其实就是你检查不成功,先结束系统调用(系统调用立马返回)  这就是非阻塞!! 而你一直给小张打电话其实就是轮询 (不断询问 有while循环)所以加在一起就是           非阻塞轮询!

 2、最后你考过了,你很开心,而是数据结构老师又告诉你明天要考试,你又没听,于是你想到了找小张,但是历史的经验告诉你肯定得打很多电话,上次手机都打欠费了。于是这次你换了一个思路,在小张告诉你再等会的时候,你就要求他不要挂断电话,直到下楼的时候再挂,这样我可以随时知道你的情况     

      这个过程其实就是阻塞!! 也就是系统调用会卡住,会被链接到子进程的一个阻塞队列中等待。

3、你又过了,你特别开心,但是操作系统明天又要考试了,于是你给小张打电话,但是你也不知道小张不会立马下来,所以你自己也带了本书,在等小张的时候自己也不会闲着没事。可以自己看会书

    这个过程描述的就是,阻塞的方式虽然简单且应用较多,但是也比较呆,因为父进程在等待的时候啥也干不了,非阻塞轮询相比较于阻塞来说,可以多做一些自己的事情,比如说我可以做一些检查的工作!

4、父进程在非阻塞轮询时可以做什么事,如果这件事任务太重到时没时间等怎么办??

——>一般来说这种事都是一些比较轻的工作,因为我们核心的任务是等待子进程,所以一般来说都是做一些检查之类的简单任务。 

3.2.3 waitpid解读

参数:

1、pid_t pid   

-1 :等待任意一个子进程  和wati等效

>0:  等待那个id和该pid相等的子进程 

2、int *status  

(1) WIFEXITED(status) : 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)     其实等价于status&0x7F

(2) WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)其实等价于(status<<8)&0xFF

(3)NULL:不关心子进程的状态

 进程本质上是一个多叉树,父进程只关心自己直系的子进程!

3、int options

0:代表阻塞等待的方式,就是子进程没结束系统调用就一直等

WNOHANG: 代表非阻塞轮询的等待方式,若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则返回该子进程的ID。 

 轮询的话必然要维护一个while循环,然后根据情况去break,不然就可能出现子进程还没结束父进程就挂了的情况

4、返回值

当正常返回的时候waitpid返回收集到的子进程的进程ID;

如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0;

如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在;

 调用出错,比方说等待的不是自己的子进程

3.2.4 多进程的代码逻辑

 1、如果是多进程的话,waitpid的第一个参数可以用-1,让父进程等待任意一个子进程,然后子进程有多少最好用一个宏,这样父进程可以知道子进程的数目,轮询的时候我们就不能一下子break掉,而是需要维护一个计数器,没等待完一个子进程就去统计一下

2、创建很多的子进程,但是具体哪个先去执行是由调度器决定的,但是我们必须知道的就是最后一个结束的必然就是父进程,因为子进程都是他创建的,所以他理所应当去回收所有的子进程 

3、进程最重要的三个核心:进程创建、进程等待、进程终止。所以我们在需要多进程的时候,我们的代码核心首先要考虑以下要素:(1)需要有循环fork创建子进程 (2)需要在合适的时候让子进程退出(常用exit)(3)父进程必须等待子进程(阻塞就是一直卡住等,非阻塞轮询就是得需要一个while循环 反复调用)  他有义务回收所有子进程!

 

相关文章:

Linux:进程的创建、终止和等待

一、进程创建 1.1 fork函数初识 #include pid_t fork(void); 返回值&#xff1a;子进程中返回0&#xff0c;父进程返回子进程id&#xff0c;出错返回-1 调用fork函数后&#xff0c;内核做了下面的工作&#xff1a; 1、创建了一个子进程的PCB结构体、并拷贝一份相同的进程地址…...

数值优化基础——基于优化的规划算法

1 最优化问题的一般形式 最优化问题:满足一系列约束的可行域内,找到使得目标函数最小的解 min ⁡ f ( x ) s.t. x...

括号匹配——(栈实现)

题目链接 有效的括号https://leetcode.cn/problems/valid-parentheses/description/ 题目要求 样例 解题代码 import java.util.*; class Solution {public boolean isValid(String str) {Stack<Character> stacknew Stack<>();for(int i0;i<str.length();i)…...

【Java 并发编程】初识多线程

前言 到目前为止&#xff0c;我们学到的都是有关 “顺序” 编程的知识&#xff0c;即程序中所有事物在任意时刻都只能执行一个步骤。例如&#xff1a;在我们的 main 方法中&#xff0c;都是多个操作以 “从上至下” 的顺序调用方法以至结束的。 虽然 “顺序” 编程能够解决相当…...

Linux下载安装MySQL8.4

这里写目录标题 一、准备工作查看系统环境查看系统架构卸载已安装的版本 二、下载MySQL安装包官网地址 三、安装过程上传到服务器目录解压缩&#xff0c;设置目录及权限配置my.cnf文件初始化数据库配置MySQL开放端口 一、准备工作 查看系统环境 确认Linux系统的版本和架构&am…...

强化学习笔记之【DDPG算法】

强化学习笔记之【DDPG算法】 文章目录 强化学习笔记之【DDPG算法】前言&#xff1a;原论文伪代码DDPG算法DDPG 中的四个网络代码核心更新公式 前言&#xff1a; 本文为强化学习笔记第二篇&#xff0c;第一篇讲的是Q-learning和DQN 就是因为DDPG引入了Actor-Critic模型&#x…...

c++继承(下)

c继承&#xff08;下&#xff09; &#xff08;1&#xff09;继承与友元&#xff08;2&#xff09;继承与静态成员&#xff08;3&#xff09;多继承及其菱形继承问题3.1 继承模型3.2 虚继承3.3 多继承中指针偏移问题 &#xff08;4&#xff09;继承和组合&#xff08;9&#xf…...

数据结构 ——— 单链表oj题:反转链表

目录 题目要求 手搓一个简易链表 代码实现 题目要求 给你单链表的头节点 head &#xff0c;请你反转链表&#xff0c;并返回反转后的链表 手搓一个简易链表 代码演示&#xff1a; struct ListNode* n1 (struct ListNode*)malloc(sizeof(struct ListNode)); assert(n1);…...

前端项目npm install报错解决的解决办法

报错问题一: [rootspug-api spug_web]# npm install npm WARN deprecated xterm4.19.0: This package is now deprecated. Move to xterm/xterm instead. npm WARN deprecated workbox-google-analytics4.3.1: It is not compatible with newer versions of GA starting with v…...

vue双向绑定/小程序双向绑定区别

Vue双向绑定与小程序双向绑定在实现方式、语法差异以及功能特性上均存在显著区别。以下是对这两者的详细比较&#xff1a; 一、实现方式 Vue双向绑定 Vue的双向绑定主要通过其响应式数据系统实现。Vue使用Object.defineProperty()方法&#xff08;或在Vue 3中使用Proxy对象&am…...

华为OD机试真题---字符串变换最小字符串

题目描述: 给定一个字符串s&#xff0c;最多只能进行一次变换&#xff0c;返回变换后能得到的最小字符串(按照字典序进行比较)。 变换规则: 交换字符串中任意两个不同位置的字符。 输入描述: 一串小写字母组成的字符串s 输出描述: 按照要求进行变换得到的最小字符串 补…...

JAVA基础面试题汇总(持续更新)

1、精确运算场景使用浮点型运算问题 精确运算场景&#xff08;如金融领域计算应计利息&#xff09;计算数字&#xff0c;使用浮点型&#xff0c;由于精度丢失问题&#xff0c;会导致计算后的结果和预期不一致&#xff0c;使用Bigdecimal类型解决此问题&#xff0c;示例代码如下…...

设计模式-创建型-常用:单例模式、工厂模式、建造者模式

单例模式 概念 一个类只允许创建一个对象&#xff08;或实例&#xff09;&#xff0c;那这个类就是单例类&#xff0c;这种设计模式就叫做单例模式。对于一些类&#xff0c;创建和销毁比较复杂&#xff0c;如果每次使用都创建一个对象会很耗费性能&#xff0c;因此可以把它设…...

【数据结构】【链表代码】随机链表的复制

/*** Definition for a Node.* struct Node {* int val;* struct Node *next;* struct Node *random;* };*/typedef struct Node Node; struct Node* copyRandomList(struct Node* head) {if(headNULL)return NULL;//1.拷贝结点&#xff0c;连接到原结点的后面Node…...

Linux 系统五种帮助命令的使用

Linux 系统五种帮助命令的使用 本文将介绍 Linux 系统中常用的帮助命令&#xff0c;包括 man、–help、whatis、apropos 和 info 命令。这些命令对于新手和有经验的用户来说&#xff0c;都是查找命令信息、理解命令功能的有力工具。 文章目录 Linux 系统五种帮助命令的使用一…...

Vueron引领未来出行:2026年ADAS激光雷达解决方案上市路线图深度剖析

Vueron ADAS激光雷达解决方案路线图分析&#xff1a;2026年上市展望 Vueron近期发布的ADAS激光雷达解决方案路线图&#xff0c;标志着该公司在自动驾驶技术领域迈出了重要一步。该路线图以2026年上市为目标&#xff0c;彰显了Vueron对未来市场趋势的精准把握和对技术创新的坚定…...

Java | Leetcode java题解之第458题可怜的小猪

题目&#xff1a; 题解&#xff1a; class Solution {public int poorPigs(int buckets, int minutesToDie, int minutesToTest) {if (buckets 1) {return 0;}int[][] combinations new int[buckets 1][buckets 1];combinations[0][0] 1;int iterations minutesToTest /…...

怎么不改变视频大小的情况下,修改视频的时长

视频文件太大怎么变小&#xff1f;不影响画质的四种方法 怎么不改变视频大小的情况下,修改视频的时长 截取结尾的时间你可以使用 ffmpeg 来裁剪视频的结尾部分。假设你想去掉视频最后的3秒钟&#xff0c;可以先使用 ffmpeg 获取视频的总时长&#xff0c;然后通过指定一个新的…...

数据结构:AVL树

前言 学习了普通二叉树&#xff0c;发现普通二叉树作用不大&#xff0c;于是我们学习了搜索二叉树&#xff0c;给二叉树新增了搜索、排序、去重等特性&#xff0c; 但是&#xff0c;在极端情况下搜索二叉树会退化成单边树&#xff0c;搜索的时间复杂度达到了O(N)&#xff0c;这…...

系统守护者:使用PyCharm与Python实现关键硬件状态的实时监控

目录 前言 系统准备 软件下载与安装 安装相关库 程序准备 主体程序 更改后的程序&#xff1a; 编写.NET程序 前言 在现代生活中&#xff0c;电脑作为核心工具&#xff0c;其性能和稳定性的维护至关重要。为确保电脑高效运行&#xff0c;我们不仅需关注软件优化&#xf…...

零门槛NAS搭建:WinNAS如何让普通电脑秒变私有云?

一、核心优势&#xff1a;专为Windows用户设计的极简NAS WinNAS由深圳耘想存储科技开发&#xff0c;是一款收费低廉但功能全面的Windows NAS工具&#xff0c;主打“无学习成本部署” 。与其他NAS软件相比&#xff0c;其优势在于&#xff1a; 无需硬件改造&#xff1a;将任意W…...

P3 QT项目----记事本(3.8)

3.8 记事本项目总结 项目源码 1.main.cpp #include "widget.h" #include <QApplication> int main(int argc, char *argv[]) {QApplication a(argc, argv);Widget w;w.show();return a.exec(); } 2.widget.cpp #include "widget.h" #include &q…...

【算法训练营Day07】字符串part1

文章目录 反转字符串反转字符串II替换数字 反转字符串 题目链接&#xff1a;344. 反转字符串 双指针法&#xff0c;两个指针的元素直接调转即可 class Solution {public void reverseString(char[] s) {int head 0;int end s.length - 1;while(head < end) {char temp …...

Rust 异步编程

Rust 异步编程 引言 Rust 是一种系统编程语言,以其高性能、安全性以及零成本抽象而著称。在多核处理器成为主流的今天,异步编程成为了一种提高应用性能、优化资源利用的有效手段。本文将深入探讨 Rust 异步编程的核心概念、常用库以及最佳实践。 异步编程基础 什么是异步…...

Linux C语言网络编程详细入门教程:如何一步步实现TCP服务端与客户端通信

文章目录 Linux C语言网络编程详细入门教程&#xff1a;如何一步步实现TCP服务端与客户端通信前言一、网络通信基础概念二、服务端与客户端的完整流程图解三、每一步的详细讲解和代码示例1. 创建Socket&#xff08;服务端和客户端都要&#xff09;2. 绑定本地地址和端口&#x…...

CSS设置元素的宽度根据其内容自动调整

width: fit-content 是 CSS 中的一个属性值&#xff0c;用于设置元素的宽度根据其内容自动调整&#xff0c;确保宽度刚好容纳内容而不会超出。 效果对比 默认情况&#xff08;width: auto&#xff09;&#xff1a; 块级元素&#xff08;如 <div>&#xff09;会占满父容器…...

C++使用 new 来创建动态数组

问题&#xff1a; 不能使用变量定义数组大小 原因&#xff1a; 这是因为数组在内存中是连续存储的&#xff0c;编译器需要在编译阶段就确定数组的大小&#xff0c;以便正确地分配内存空间。如果允许使用变量来定义数组的大小&#xff0c;那么编译器就无法在编译时确定数组的大…...

《C++ 模板》

目录 函数模板 类模板 非类型模板参数 模板特化 函数模板特化 类模板的特化 模板&#xff0c;就像一个模具&#xff0c;里面可以将不同类型的材料做成一个形状&#xff0c;其分为函数模板和类模板。 函数模板 函数模板可以简化函数重载的代码。格式&#xff1a;templa…...

力扣热题100 k个一组反转链表题解

题目: 代码: func reverseKGroup(head *ListNode, k int) *ListNode {cur : headfor i : 0; i < k; i {if cur nil {return head}cur cur.Next}newHead : reverse(head, cur)head.Next reverseKGroup(cur, k)return newHead }func reverse(start, end *ListNode) *ListN…...

抽象类和接口(全)

一、抽象类 1.概念&#xff1a;如果⼀个类中没有包含⾜够的信息来描绘⼀个具体的对象&#xff0c;这样的类就是抽象类。 像是没有实际⼯作的⽅法,我们可以把它设计成⼀个抽象⽅法&#xff0c;包含抽象⽅法的类我们称为抽象类。 2.语法 在Java中&#xff0c;⼀个类如果被 abs…...