• 【操作系统】6/35子进程


    EXEC函数

    execl进程功能重载。

    execl函数有很多个组成,做的事情都是差不多的。

    开发者可以使用它,加载一个进程的用户空间(读取一个现有程序的功能),赋予子进程。

    一个进程的核心内容都在用户层。而不是内核层。

    如果想让子进程有浏览器进程,那么就可以把浏览器贴到子进程里。

    也就是说,直接拿过来用,贴给子进程。

    例如将一个父进程processA,没有浏览器功能,但是想给一个,因此使用execl()函数,浏览器会有路径,例如是/bin/browser,那么就利用execl函数,启动加载浏览器程序的用户层,浏览器也有用户层,函数就可以读取浏览器的用户层,然后复制给自己,用的函数是load(覆盖掉进程原有的用户层),此时,我们的processA进程,就可以使用浏览器了。

    此时,重载完毕,该进程具备了浏览器特征以及功能。

    可以对现成的数据进行重载,就可以直接拿过来了。


    execl()函数

    用which查看路径.

    execl("/bin/browser", "firefox", "https://www.baidu.com", NULL);

    参数最后必须传NULL,否则不能执行。

    参数:

    1. 程序位置,就是绝对路径
    2. 参数数量根据需求或命令数量不同,因为每个命令或进程的命令参数不同,例如ls有一个,浏览器有两个(假设)
    3. execl每个参数都是字符串

    用which找到浏览器的绝对路径,然后用execl函数在子进程中编写。

    1. execl("/usr/bin/firefox","firefox","www.baidu.com",NULL);
    2. exit(0);

    因此在执行之后,先父进程进行子进程创建,然后子进程进入else if,实现execl函数,也就是进入了浏览器的百度页面。

    因此,子进程就是浏览器。子进程是重载出来的浏览器。

    exec是一个覆盖,进行了重载操作之后,原来的内容就彻底消失了。

    execl:如果子进程有自定义任务和工作,需要在fork之后,和exec之前完成。也就是pid=0的里面。

    正是因为exec之后,子进程就被覆盖了。exec就不能执行原有的功能了。

    因此,exit(0)这句话根本没有执行,我们进程的退出是关闭浏览器的操作才退出的。


    execlp()

    是exec函数变体。

    区别:就是第一个参数并不用放置绝对路径了,因此,直接写进程名字就好了,并没有什么区别。

    execv()

    没有l了,l是每一个参数都是一个字符串。

    但是v是数组,都是放入数组里。

    1. const char* array[]={"firefox","www.baidu.com"};
    2. execv("/usr/bin/firefox",array);

    作用和效果都是一样的。根据用户喜好。

    execvp()

    两种的结合。

    execvp("firefox", array);

    execle(), execve()也是两种新的函数,只是细微的变化。


    关键字

    l:每个参数都是字符串。

    v:这种exec接口需要用户将命令参数封装在数组中,传参时传入数组首地址。

    p:path,,可以不用填写绝对位置,可以自行查找程序位置。

    e:进程创建时,有环境变量,系统有一个环境变量表,进程创建后,系统会拷贝一份环境变量给进程。

    e表示的就是自定义环境变量,替换进程默认的环境变量。


    僵尸进程

    正常fork的流程之后,如果查看进程状态,会发现,父进程是./app,但是子进程却是:[app] ,父进程状态是S+,是睡眠,而子进程是Z+,是Linux中的僵尸进程。

    丧尸进程:人——病毒致死——结束——攻击性极强(危害)

    可以用来描述这个僵尸进程。

    进程——异常原因——内存泄漏(危害)

    是因为没有及时处理。

    这些问题的解决方案就是:回收处理

    例子:两个进程,父子pc。

    描述:子进程先于父进程结束,然后父进程未对子进程进行有效的回收操作,子进程变成僵尸进程,导致内存泄漏。

    与孤儿进程不同:是父进程先于子进程结束。

    子先死,是僵尸,父先死,是孤儿。

    exit函数的底层有另一个函数:_EXIT函数,负责释放回收进程资源的。是系统内核kernel负责的,执行并释放。

    完全释放用户层,不会有残留。

    但是内核空间,并没有回收干净,pcb没有去动。内核层部分回收。

    因此,PCB残留,就是内存泄漏。危害是最小的。

    pcb不小,有的东西还有额外的内容。

    进程创建前提:有可用pcb。

    也就是说,你得能先创建出一个pcb,创建之后,才能创建进程。

    危害:

    1. 内存泄漏危害,占用系统内存,消耗资源
    2. 进程创建的前提是有可用pcb,如果僵尸进程过多,占用pcb,影响进程创建

    wait函数

    回收作用,回收函数。

    用来处理pcb。

    pid_t zpid=wait(NULL)

    wait函数是一个阻塞函数,阻塞回收僵尸进程(的PCB),成功一次回收一个pcb,而不是调用一次全部回收,如果回收1000个就调用1000次。

    成功返回僵尸进程pid,失败返回-1,绝大多数不会失败,如果没有子进程调用了wait,就失败了。

    头文件

    此时执行完wait函数,僵尸进程消失了。

    1. zpid=wait(NULL);
    2. if(zpid>0){
    3. printf("zpid is %d\n",zpid);
    4. }

    wait函数会等待子进程结束之后,再退出。

    也就是说是阻塞函数,父进程到wait之后阻塞了,子进程退出之后开始执行父进程。

    为什么内核不把pcb回收掉?

    pcb让父进程回收是有原因的,例如子意外了,父就需要处理,但是子的遗骸不可能会消失,也就是说,必须父进程回收,父进程可以通过pcb分析出子进程回收的愿意的。

    父进程是子进程的直接负责人。内核并没有这个权限。

    退出信息存在PCB中。

    wait(int* status),验shi信息存入status,NULL就不进行验。

    如果父进程需要对子进程进行退出校验,那么wait函数必须使用status参数传出子进程退出信息。

    WIFEXITED(status)/WEXITSTATUS(status)

    status退出信息,系统会提供一些校验函数:

    WIFEXITED(int* status)

    exit(0)和return 0都属于正常的退出。正常退出范畴。

    if(WIFEXITED(status))

    如果是真的,就是正常退出,反之是假的。

    获取退出码:WEXITSTATUS(status),一般是上一个是真的,才会调用这个。

    返回status的退出码或返回值。

    WIFSIGNALED(status)/WTERMSIG(status)

    除了正常退出,还会有异常退出,是信号查找。

    Linux经典是信号,一般都是用于杀死进程。

    WIFSIGNALED(status),判断是否是信号杀死,返回真假。

    WTERMSIG(status),返回杀死子进程的信号编号。

    子进程退出校验

    1. int main(void){
    2. pid_t pid;
    3. int i=0;
    4. for(i;i<2;++i){
    5. pid=fork();
    6. if(!pid)break;
    7. }
    8. if(pid>0){
    9. pid_t zpid;
    10. int status;
    11. printf("im father, my pid is %d\n,im waiting...",getpid());
    12. while((zpid=wait(&status))>0){
    13. //PCB推出校验
    14. if(WIFEXITED(status))printf("zombie pid %d return value exitcode %d\n",zpid,WEXITSTATUS(status));
    15. if(WIFSIGNALED(status))printf("zombie pid %d kill signal NO %d\n",zpid,WTERMSIG(status));
    16. }
    17. }else if(!pid){
    18. if(!i){
    19. printf("im child%d,my pid is %d\n,im sleeping...",i,getpid());
    20. sleep(2);
    21. exit(0);//正常推出
    22. }else{
    23. //异常推出
    24. while(1){
    25. printf("im child%d,my pid is %d\n",i,getpid());
    26. sleep(2);
    27. }
    28. }
    29. }else{
    30. perror("call failed\n");
    31. exit(1);
    32. }
    33. return 0;
    34. }

    结果就是,0号子进程通过正常的方式回收并退出。

    但是1号子进程一直在执行,我们通过kill -11 zpid进行异常退出。

    就返回了异常退出的信息。

  • 相关阅读:
    深入理解嵌入式系统【基于Arduino的嵌入式系统入门与实践】相关基础知识概述:嵌入式系统/技术(定义、分类、组成、简介);Arduino开发板分类;VCC,GND;模拟信号和数字信号;杜邦线,面包板
    如何为 SAST 工具设置误报基准?
    PHP毕业设计毕设辅导课(1):PHP 基础语法
    大数据各个组件对读写数据的优化思考总结
    04.爱芳地产项目小程序全栈项目经验(已上线)
    8月20日计算机视觉理论学习笔记——神经网络与BP算法
    Linux扩展swap分区
    Kubernetes(K8S) kubesphere 介绍
    2.K3S+Rancher之在线安装
    八位阿里p8耗时十年总结出Java面试复盘手册,带你实现逆风翻盘
  • 原文地址:https://blog.csdn.net/callmejielun/article/details/126247101