小实验(谨慎测试)
1. 进程退出码的引出
2. 进程码的使用
3. 进程退出
3.1 进程退出情况
==进程退出分三种情况:==
1.代码运行完毕,结果正确 -- return 0;
2.代码运行完毕,结果不正确 -- 根据退出码判断错误情况
3.代码没有运行完毕,程序出现异常,退出码毫无意义
3.2 进程退出方式
对于第二第三种情况:exit()函数的底层实现就是通过调用_exit()系统接口来实现的。
我们再来看一种现象:
4. 进程等待
在之前进程状态的学习中,我们认识到一种状态--“僵尸状态” --"Z" ,僵尸状态的原因是由于父进程未接收子进程退出信息导致的,子进程一直处于僵尸状态等待父进程接收其退出信息。僵尸进程是一种危害,存在内存泄漏的问题,该如何解决呢?
--通过进程等待的方式解决僵尸问题,如何解决:进行回收子进程资源并接收子进程退出信息
4.1 进程等待方式
4.1.1 wait方法
4.1.2 waitpid方法
在正式认识waitpid方法之前我们需要先认识一个输出型参数status(专门接收并输出给操作系统的参数)
再谈进程退出
进程退出会使进程进入僵尸状态,等待父进程或者操作系统接收退出结果,这时候进程会将自己的退出结果写入到自己的task_struct(PCB)
而wait/waitpid是系统调用接口,让操作系统去读取子进程的task_struct(PCB)的退出信息
==进程退出其他资源可以释放,但是PCB必须保留(确保操作系统/父进程接收到子进程退出的信息)==
5. 等待实现(操作系统)
操作系统是如何实现这种等待状态的呢? -- 分为阻塞等待和非阻塞等待
5.1 阻塞等待
5.2 非阻塞等待
5.3 阻塞等待和非阻塞等待的区别
从上述代码还看不出来二者存在什么区别:
阻塞等待:一直处于等待状态
非阻塞等待:在等待的过程中还可以处理其他事务(主要体现在轮询等待的情况)
5.4 轮询等待的实现
6. 进程程序替换
在运行进程时,创建子进程的目的是什么?
- 想让子进程执行父进程代码的一部分:也就是执行父进程对应的磁盘代码的一部分
- 想让子进程执行一个全新的程序:让子进程想办法加载磁盘上指定的程序,并执行新程序的代码和数据
(这种子进程加载新程序并运行的行为,我们称之为进程的程序替换)6.1 替换函数
替换函数的作用:将指定的程序(这里的程序指的是在磁盘当中保存的代码和数据)加载到内存当中,让指定的进程进行执行#include <unistd.h> int execl(const char *path, const char *arg, ...); int execlp(const char *file, const char *arg, ...); int execle(const char *path, const char *arg, ...,char *const envp[]); int execv(const char *path, char *const argv[]); int execvp(const char *file, char *const argv[]);
要使用替换函数首先需要先找到磁盘当中的程序,其次执行命令存在选项,例如:ls -a -l
如何找到呢?-根据第一个参数 const char* path,来找到程序所在位置
根据后面两个参数来实现执行命令参数的选项
(其中括号中带... 这表示的是可变参数列表【可以给C语言传递多个不同个数的参数】)
很多函数都是存在可变参数列表的,例如上述的printf和scanf,这也就是为什么在打印时可以以不同的形式打印%d/%s等6.2 使用替换函数
6.3 进程替换的原理
用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种exec函数以执行另一个程序。
当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。
调用exec并不创建新进程,所以调用exec前后该进程的id并未改变
写时拷贝时,程序和数据就会在物理内存上占用新空间(保证了进程的独立性)
==这时候也就可以深刻理解shell创建子进程来执行对应的命令(王婆雇佣实习生的案例),==
shell命令行解释器也是进程如果不创建子进程,
直接进行程序替换那么我们也就无法通过shell接收返回结果,
如果一旦是非法进程可能直接使得shell挂掉(这样也进一步保护shell)
子进程挂掉不会影响父进程,且父进程可以接收子进程的错误原因并返回给用户7. exec* 系列函数的分析使用
其中子进程发生进程替换并不会影响父进程7.1 对后缀进行分析
实际上默认的环境变量,即便你不传,子进程也能获取
那如果即想获取自定义的环境变量又想获取系统的环境变量该如何操作呢?
针对这些后缀分析完毕后,要了解一个概念,之前的学习过程中总是说程序要运行必须先加载到内存当中,如何加载呢?
在Linux下采用的就是exec系列的接口 -- exec系列的函数又被称之为加载器
提问我们在编写程序时main(int argc,charargv[ ],char env[ ])是先加载呢?还是先执行main函数?
答案:先加载,main函数也是函数,也需要被调用传参,这些工作都离不开加载器
而main函数中的三个参数正是从exec*系列函数当中的参数而来(环境变量/命令行参数)
即便main函数不传递参数,子进程照样可以拿到系统默认的环境变量,通过environ表/虚拟地址空间的方式让子进程获取8. 替换自己写的程序
以上的学习进程替换都是替换的系统命令,那如果我们想实现通过子进程替换我们自己编译的代码该如何实现呢?C语言
这只是C语言的情况,那么是否可以实现不同语言中的程序替换呢?
答案:可以C++
Python
shell
程序替换,可以让任何后端语言调用起来(甚至java也可以,但是非常麻烦:java虚拟机的存在)系统命令
总结