一、进程创建
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循环 反复调用) 他有义务回收所有子进程!
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |