• 进程控制详解


    目录

    fork函数

    进程退出

    进程等待

    阻塞与非阻塞

    程序替换

    制作简易的shell


    fork函数

    写时拷贝

    如下图,在子进程要修改内容时,os会让子进程发生缺页中断,然后为它开辟一块空间,然后把

    进程的值拷贝过来

    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/waitpid等

    待子进程退出

    为什么要让父进程等待?

    通过获取子进程退出的信息,能够得知子进程执行结果

    可以保证:时序问题,子进程先退出,父进程后退出

    进程退出的时候会先进入僵尸状态,会造成内存泄漏的问题,需要通过父进程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从等待队列拿到运行队列,从而被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本

    身执行的,内建命令

     

  • 相关阅读:
    再生之术:Root 密码遗忘的 CentOS8 Stream 解决方案
    数据治理在数字化转型中的重要性
    Create Fillable PDF Crack
    黑客专业术语认知
    Elasticsearch系列(六)ES数据搜索之基本流程
    TestStand-从LabVIEW创建TestStand数据类型的簇
    软件测试的几种方法
    HTML5-2-链接
    Pygame中Sprite的使用方法6-4
    c#动态保留小数位数的数值格式化方法实例----从小数点后非零数字保留两位进行四舍五入
  • 原文地址:https://blog.csdn.net/weixin_58867976/article/details/126714809