fork函数
进程退出
进程等待
阻塞与非阻塞
程序替换
制作简易的shell
fork函数
写时拷贝
如下图,在子进程要修改内容时,os会让子进程发生缺页中断,然后为它开辟一块空间,然后把
父****进程的值拷贝过来
fork常规用法
fork常规用法 一个父进程希望复制自己,使父子进程同时执行不同的代码段
一个进程要执行一个不同的程序
fork调用失败的原因
系统中有太多的进程
实际用户的进程数超过了限制
另外,创建进程是有成本的(时间+空间)
进程退出
main函数的return值是进程的退出码,用来衡量结果对不对
代码运行完毕****结果正确
退出码为0,一般表示是success!
代码运行完毕****结果不正确
退出码为非0,则表示failed,因为结果不正确的原因有很多种,所有退出码也就有多种就比如考
试考得差你爸妈会问****你原因,考得好一般不会问
代码异常终止
如下图,代码异常终止后,程序就会崩溃,这时进程码也就没有意义了!
main函数return,代表进程退出,非main函数退出,代表函数返回
exit在任意地方调用,都代表终止进程,参数是退出码!
如下图,printf函数中没有\n,无法在printf语句结束后刷新缓冲区,数据是暂时被保存在输出缓冲
区中,exit或main中的return本身就会要求系统进行缓冲区刷新!所有才会打印出来
_exit终止进程,是强制终止进程,不会进行进程的后续收尾工作,比如刷新缓冲区(用户级缓冲区)
exit与_exit的区别,如下图
进程退出,在系统层面,少了一个进程,free PCB,free mm_struct,free 页表和各种映射关
系,****代码+数据申请的空间也要被free掉!
进程等待
父进程fork创建子进程,是为了让其帮助父进程完成某种任务,而父进程需要通过wait/wait等待子
进程退出
为什么要让父进程等待?
通过获取子进程退出的信息,能够得知子进程执行结果
可以保证:时序问题,子进程先退出,父进程后退出
进程退出的时候会先进入僵尸状态,会造成内存泄漏的问题,需要通过父进程wait,释放该子进程
占用的资源!
另外,进程一旦变成僵尸状态,那kill -9也无能为力,因为谁也没有办法杀死一个已经死去的进程
**wait/waitpid **
注意:僵尸:PCB保存进程退出时的退出数据!
下面的stat_loc是一个输出型参数,来反映是进程退出的三种结果中的哪一个
如下图,子进程的僵尸状态被解除且退出了!** 而且也保证了子进程先退出,父进程后退出**
等待指定一个进程,如下图中的id就是子进程的编号
-1表示等待任意一个子进程,等价于wait!
也有可能会等待失败,比如随便传一个id
如下图,父进程拿到什么status结果,一定和子进程如何退出强相关!!!这个结果也就是前面所
提到的进程退出的三种结果之一,如果是收到的退出码,则表示代码运行完毕,而如果是收到的某
种信号,则表示代码异常终止,因为其本质是这个进程因为异常问题,导致自己收到了某种信号!
为了反应从子进程收到的信息,本来有32个bit位,就只用16个,高位上的8个bit来表示退出码,
地位上的7个表示终止信号,而中间的一个
最终一定要让父进程通过status得到子进程的结果!
如下图,测试的是代码运行完毕不正确结果的情况
代码异常终止的情况
除零异常
进程被干掉了
如下图,也可以用宏的方式来得到退出码
bash是命令行启动的所有进程的父进程!而bash一定是通过wait方式得到子进程的退出结果,所
以****我们能通过echo$?查到子进程的退出码!
阻塞与非阻塞
**确定是阻塞还是非阻塞由wait中的第三个参数options确定 **
阻塞:比如你要和你的某个同学去图书馆复习,你要他给你画重点,你已经到了他家楼下,但
是他要你等他30分钟,你就一直等,而且什么都不干,就一直注视着他家楼上。options为0
非阻塞:你等他的过程中,时不时的给他打电话询问他还有多久。options为WNOHANG,可能需
要多次检测:基于非阻塞等待的轮询方案
阻塞的本质:其实是进程的PCB被放入了等待队列,并将进程的状态改为S状态
返回的本质:进程的PCB从等待队列拿到R队列,从而被CPU调度
非阻塞分为几种情况
子进程根本没有退出
子进程退出,waitpid(调用成功或者失败)
ret==0时,子进程没有退出,但是waitpid等待是成功的,需要父进程重复进行等待
ret>0时,子进程退出了,waitpid也成功了,获得了对应的结果
ret<0时,等待失败
程序替换
目前我们创建子进程的目的:if else让子进程执行父进程的代码的一部分!
为什么需要程序替换?
如果想让子进程执行****一个"全新的程序",就需要进行程序替换
概念
进程不变,仅仅替换当前进程的代码和数据的技术,叫做进程的程序替换!没有创建新的进程!
程序本质就是一个文件!
文件=程序代码+程序数据
进程的程序替换使用
如下图,将本来要执行printf("哈哈")替换成了执行ls命令
程序替换的本质就是把程序的进程代码+数据,加载到特定进程的上下文中!
C/C++程序要运行,必须得通过加载器先加载到内存中!加载器也就是exec*程序替换函数!
如下图,子进程进行程序替换,但父进程没有被影响,是因为进程具有独立性,虽然父子进程代码
是共享的,但是进程程序替换会更改代码区的代码,也要发生写时拷贝
如下图,以及前面所述可知,只要进程的程序替换成功,就不会执行后续代码,意味着exec*函
数,成功的时候,不需要返回值检测。只要exec*返回了,就一定时因为调用失败了!!!
各个程序替换函数的基本使用
命名理解
l(list) : 表示参数采用列表
v(vector) : 参数用数组
p(path) : 有p自动搜索环境变量PATH
**e(env) : 表示自己维护环境变量 **
execl
execv
execlp
execvp
execle
execve:系统调用
上述所有的接口,看起来是没有太大区别的,只有一个就是参数的不同!而有这么多的接口,是为
了满足不同的应用场景
如下图,可以同时创建多个可执行程序
制作简易的shell
打印命令提示符
这里没用\n,是因为不符合预期,因为输入命令时是和命令提示符在同一行的,因为没有\n,所以需要fflush来****刷新缓冲区
获取命令字符串
定义一个宏,便于修改字符串大小,用fgets来获取字符串,而不用scanf则是因为输出的命令可能
会包含空格,如ls -a -l,同时还需要将回车去掉,如absds\n\0,要将\n置为\0
将字符串进行分隔
首先在循环最开始创建一个指针数组,将字符串按空格分隔开,然后统计在一个数组中,便于程序替换时使用
执行第三方命令
获取子进程返回的退出码
如下图,返回上层命令输入后,并没有得到想要的结果,所以还需要检测命令是否是需要shell本
身执行的,内建命令
版权归原作者 风影66666 所有, 如有侵权,请联系我们删除。