当前位置:   article > 正文

Linux——进程控制1|再谈fork()|fork常规用法和调用失败原因|进程终止|main函数返回值|exit和_exit|相同点|不同点 |总结 |进程等待_fork一个子进程去执行容易崩溃的代码段

fork一个子进程去执行容易崩溃的代码段

目录

再谈fork() 

fork常规用法和调用失败原因 

进程终止 

main函数返回值 

exit和_exit 

相同点 

不同点 

总结 

 进程等待

wait验证 

 wait pid

获取子进程退出的结果 

总结 


再谈fork() 

进程调用fork,当控制转移到内核中的fork代码后,内核做:

分配新的内存块和内核数据结构给子进程


将父进程部分数据结构内容拷贝至子进程


添加子进程到系统进程列表当中


fork返回(当return的时候),开始调度器调度

 创建子进程,给子进程分配对应的内核结构,这个结构必须子进程自己独有,因为具有独立性,理论上子进程也要有自己的代码和数据,可是一般而言,我们没有加载的过程,也就是说子进程没有自己的代码和数据。所以,子进程只能使用父进程的代码和数据

代码:都是不可被写的,只能读取,父子共享没问题

数据:可能被修改,所以,必须分离。

对于数据而言创建进程的时候,就直接拷贝分,这种情况可能会拷贝一些不会用到的空间,即使用到了,也只能读取

 

 编译器编译程序的时候,尚且知道节省空间。创建子进程,不需要将不会被访问的,或者只会读取的数据,拷贝一份。将来被父或子进程写入得数据会被拷贝。一般而言即便是OS,也无法提前知道哪些空间可能会被写入。所以操作系统选择了,写时拷贝技术,将父子进程的数据分离。

fork之后代码共享,共享的是所有的代码

我们的代码汇编之后,会有很多行代码,而且每行代码加载到内存之后,都有对应的地址

当进程没执行完时候,可能被切换,下次回来,还会从之前切换的位置继续运行,所以CPU要记住这个位置,CPU中有对应的寄存器数据来记录这个当前位置

 pc指针存的是当前正在执行代码的下一行代码的地址,寄存器在CPU内只有一份,但寄存器内的数据(进程的上下文数据)可以有多份

父子进程可以各自调度上下文数据,各自会修改EIP,但是已经不重要了,子进程认为自己的EIP起始值,就是fork之后的代码,子进程从after开始跑,不代表fork之前的代码它看不到

fork常规用法和调用失败原因 

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


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

系统中有太多的进程


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

进程终止 

1. 进程终止,操作系统都做了什么?

释放进程申请的相关内核数据结构和对应的数据和代码,本质就是释放系统资源

2.进程终止常见的方式有哪些?

a.代码跑完,结果正确           b.代码跑完,结果不正确          c.代码没有跑完,程序崩溃了(下面例子)

 程序崩溃

main函数返回值 

main函数的返回值并不总是0,return 后面跟的数字是进程退出码,进程退出码表示进程是否正确返回,如果返回0则运行结果正确,如果非0,则结果不正确

查看退出码:echo $?

 我们把进程退出码修改为10,第一次查看退出码结果是10,之后查看结果就会是0,这是因为echo $?获取的是最近一个进程执行完毕后的退出码

main函数返回值的意义,返回给上一级进程(如父进程),用来评判该进程执行结果用的,可以忽略。

我们可以通过退出码判断出运行错误的原因,不同的退出码代表着不同的出错信息

strerror可把一个整数转换成字符串

 查看退出码

 我们可以看到0代表成功

 

 我们可以自己使用这些退出码和含义,我们也可以自己定义退出码和推出含义,如return 10,我们可认为10是File exists

 程序崩溃的时候,退出码无意义,一般而言程序还没执行到return就会崩溃,只有main函数里的return是进程退出,其它函数return起返回作用

exit和_exit 

相同点 

 exit

 _exit

 测试exit

 退出码 111,说明exit在代码的任何地方调用都是直接终止进程

 测试_eixt

 说明exit和_exit共同之处,一执行就退出

不同点 

 正常运行

 去掉\n,由于没了\n,就无法刷新缓冲区,也就是说要打印的内容此时存在了输出缓冲区当中,但exit此时也正常运行

 我们换成_exit

 此时没有打印出来,也就是_exit不会刷新缓冲区

 加上\n之后,正常运行

 

总结 

 

 exit最后也会调用_exit, 但在调用_exit之前,还做了其他工作
1. 执行用户通过 atexit或on_exit定义的清理函数。
2. 关闭所有打开的流,所有的缓存数据均被写入

3. 调用_exit

printf不加\n数据保存在“缓冲区”当中,这个缓冲区一定不在操作系统内部,如果是操作系统维护的,缓冲区_eixt也能刷新,该缓冲区只能在库函数,如C标准库

 进程等待

子进程退出,父进程不管子进程,子进程处于僵尸状态——若不回收会导致内存泄漏 

父进程如何得知子进程状况?

上面这些问题都需要进程等待来完成

进程等待的必要性:

之前讲过,子进程退出,父进程如果不管不顾,就可能造成‘僵尸进程’的问题,进而造成内存泄漏。


另外,进程一旦变成僵尸状态,那就刀枪不入,“杀人不眨眼”的kill -9 也无能为力,因为谁也没有办法杀死一个已经死去的进程。


最后,父进程派给子进程的任务完成的如何,我们需要知道。如,子进程运行完成,结果对还是不对,或者是否正常退出。


父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息


 

wait验证 

 

 子进程跑完后进入僵尸状态,没有被回收

我们使用wait回收它,wait是等待一个进程直到这个进程的状态发生变化(如有R或S变为Z),如果回收成功,返回子进程PID,失败了返回-1

参数 status 保存着子进程退出时的一些状态(包括 task_struct、thread_info及内核栈等)它是一个指向 int 类型的指针;如果不在意子进程的结束状态值,只想把这个僵尸进程消灭掉(实际上,大多数时候都是这样做的),则可以将这个参数设为 NULL,即

pid = wait(NULL);        // 不管子进程的结束状态,直接杀死进程

 使用wait,由于前面子进程sleep5秒钟,这个时候父进程调用wait是在阻塞式的等待

 通过对比我们发现,子进程已经被回收了,子进程由S切换到Z,立马被回收

 进程的一般写法就是fork+wait/wait pid

 wait pid

 

 第一个参数:某进程的PID,若是-1,则代表任意一个进程,与wait等效,若是0,表示等待指定进程

                   

第二个参数:输出型参数和wait的status一模一样

第三个参数:默认为0,表示阻塞等待

返回值>0代表等待成功,<0代表等待失败

waitpid(pid,NULL,0)=wait(NULL),验证这一结论,我们发现结果和上面一致

 

获取子进程退出的结果 

wait和waitpid,都有一个status参数,该参数是一个输出型参数,由操作系统填充。


如果传递NULL,表示不关心子进程的退出状态信息。否则,操作系统会根据该参数,将子进程的退出信息反馈给父进程。

 exit(15),status是3840

 status并不是按照整数来整体使用的。而是按照比特位的方式,将32个比特位进行划分,其中我们学习的是低16个

status构成:

status不能简单的当作整形来看待,可以当作位图来看待,具体细节如下图(只研究status低16比特
位):

 15-8叫次低8位表示子进程退出时的退出码

如果想得到子进程退出结果,右移8位,然后和1111 1111按位与,此时拿到了退出码15

 

 父进程可以通过子进程的退出码来查看子进程的具体情况

程序异常退出或者崩溃,本质是操作系统杀掉了该进程

操作系统如何杀掉?

本质是通过发送信号的方式!下图都是信号

 

 

 其实没有0信号,如果收到的信号是0,说明我们的进程是正常跑完的,测试异常

 

 8号信号是,8号信号是浮点数错误,这里的0代表退出码无意义,因为我们的退出码是15

如果我们用kill -9 ,子进程也会收到9号信号

程序异常,不仅是内部代码有问题,也可能是外部杀掉的

总结 

父进程通过wait/waitpid可以拿到子进程的退出结果(退出码和推出信号), 不能用全局变量来代替退出码

如果父进程在子进程结束之前退出,则子进程将由init接管。init将会以父进程的身份对僵尸状态的子进程进行处理。

使用wait或waitpid,当子进程退出的时候,父进程还存在,从某种意义上说wait/waitpid可以让进程退出具有一定的顺序性,这让设置的目的让父进程将来可以有更多的收尾工作

僵尸进程:至少要保留该进程的PCB信息,task_struct里面保留了任何进程退出时的退出结果信息。wait和waitpid本质是读取子进程的task_struct的结构

wait和waitpid有权利访问是task_struct(内核数据结构)因为wait和waitpid是系统调用的 

waitpid详解 

 

 如果用位运算获取进程编号和退出码会比较麻烦,系统给我们提供了宏,我们可直接拿来使用

WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)
WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)

 

当waitpid()第三个参数设置为WNOHANG的时候 ,父进程就成了非阻塞状态

Linux是用C语言写的->系统调用接口->OS自己提供的接口->就是C语言函数->系统提供的一般大写的标记位如WNOHANG,其实就是宏

#define WNOHANG 1

WNOHANG其实就是wait no hang(夯住了),夯住了在系统层面上就是这个进程没被CPU调度。此时要么是在阻塞队列中,要么等待被调度。

非阻塞等待:如果父进程检测子进程的退出状态,发现子进程没有退出,我们的父进程通过调用waitpid来进行等待,如果子进程没有退出,waitpid这个系统调用,立马返回。

阻塞等待一般都是在内核中阻塞,阻塞队列会等待被唤醒,一般一个进程被阻塞了就会被切换

伪代码:

非阻塞等待会有一个基于非阻塞调用的轮询检测方案(就是不停的进行访问,当条件不满足时会做其他事情)

  1. #include <iostream>
  2. #include <vector>
  3. #include<stdio.h>
  4. #include <stdlib.h>
  5. #include <unistd.h>
  6. #include <sys/wait.h>
  7. typedef void (*handler_t)(); //函数指针类型
  8. std::vector<handler_t> handlers; //函数指针数组
  9. void fun_one()
  10. {
  11. printf("这是一个临时任务1\n");
  12. }
  13. void fun_two()
  14. {
  15. printf("这是一个临时任务2\n");
  16. }
  17. // 设置对应的方法回调
  18. // 以后想让父进程闲了执行任何方法的时候,只要向Load里面注册,就可以让父进程执行对应的方法喽!
  19. void Load()
  20. {
  21. handlers.push_back(fun_one);
  22. handlers.push_back(fun_two);
  23. }
  24. int main()
  25. {
  26. pid_t id = fork();
  27. if(id == 0)
  28. {
  29. // 子进程
  30. int cnt = 5;
  31. while(cnt)
  32. {
  33. printf("我是子进程: %d\n", cnt--);
  34. sleep(1);
  35. }
  36. exit(11); // 11 仅仅用来测试
  37. }
  38. else
  39. {
  40. int quit = 0;
  41. while(!quit)
  42. {
  43. int status = 0;
  44. pid_t res = waitpid(-1, &status, WNOHANG); //以非阻塞方式等待
  45. if(res > 0)
  46. {
  47. //等待成功 && 子进程退出
  48. printf("等待子进程退出成功, 退出码: %d\n", WEXITSTATUS(status));
  49. quit = 1;
  50. }
  51. else if( res == 0 )
  52. {
  53. //等待成功 && 但子进程并未退出
  54. printf("子进程还在运行中,暂时还没有退出,父进程可以在等一等, 处理一下其他事情??\n");
  55. if(handlers.empty()) Load();
  56. for(auto iter : handlers)
  57. {
  58. //执行处理其他任务
  59. iter();
  60. }
  61. }
  62. else
  63. {
  64. //等待失败
  65. printf("wait失败!\n");
  66. quit = 1;
  67. }
  68. sleep(1);
  69. }
  70. }
  71. return 0;
  72. }

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/菜鸟追梦旅行/article/detail/210716
推荐阅读
相关标签
  

闽ICP备14008679号