补充几个知识:
一、fork函数初识(见之前)
写实拷贝
二、fork常规用法
一个父进程希望复制自己,使父子进程同时执行不同的代码段,例如:父进程来等待客户端的请求,生成子进程来处理请求
一个进程要执行不同的程序,例如:子进程从fork返回后,调用exec函数
三、fork调用失败的原因
创建进程是需要很大成本的
一、进程退出场景
为什么main总会return 0?意义在哪?
main函数return,代表进程退出,非main函数return叫做函数返回
exit():进程终止,头文件stdlib,在任意地方调用都代表终止进程,参数是退出码
#include
#include
#include
int main()
{
printf("hello word!");
sleep(4);//数据被暂存到输出缓冲区中
exit(EXIT_SUCCESS);
//return 0;
//都能够看到hello word被打印,说明刷新了缓冲区
//其原因就是main return or exit 本身就会要求系统,进行缓冲区刷新!
}
_exit():终止进程,头文件unistd,强制终止进程,不要进行进程的后续收尾工作,比如刷新缓冲区(指的是用户级缓冲区)
进程退出,OS层面做了什么?
系统层面,少了一个进程:free PCB,free mm_struct, free页表和各种映射关系
进程为什么要等待?
父进程fork之后,可能有这样一种情况:父进程需要子进程完成某种任务,这样父进程就需要知道子进程完成的情况,所以一般需要父进程通过wait/waitpid等待子进程退出,这种现象就叫做进程等待
为什么要父进程等待?
返回值: 成功返回被等待进程pid,失败返回-1。
参数: 输出型参数,获取子进程退出状态,不关心则可以设置成为NULL
例子:
可以观察到一开始有两个进程同时运行,之后有一个进程变成了僵尸进程,又过了一段时间这个僵尸进程也结束了,这个就可以说明首先wait是可以回收僵尸进程的,同时wait的返回值如果正常返回就是等待那个进程的pid,如果异常返回就是返回-1
头文件与wait相同:pid_ t waitpid(pid_t pid, int *status, int options);
返回值:
参数:
pid:
status(输出型参数):
WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)
WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)
例子:(一定要让父进程通过status得到进程执行的结果)
这里的status就不是简单的一个整型了,我们知道它有32个比特位,但是判断代码运行结果:只使用低16个比特位!高16比特位暂时不考虑(如果没有收到信号代表代码是运行完成的,没有异常终止);次低8位代表的就是进程退出的退出码,前7位就是进程的终止信号,如果程序没有异常终止那么就为0
用status来获取进程的退出码或者退出信号
下面这个例子是程序被异常终止,也就是接受到了信号
bash是命令行启动的所有进程的父进程,它一定是通过wait的方式得到子进程的退出结果,所以我们能看到echo $?能够查到子进程的退出码
操作系统给我们提供了两个宏,可以就是让我们每次查看退出码的时候都进行位操作,一个是WIFEXITED如果没有收到退出信号就为真,就可以用WEXITSTATUS来获取退出码
options:
进程不变,仅仅替换当前进程的代码和数据的技术叫做进程的程序替换
为什么要进行程序替换?
想让子进程执行一个全新的程序
什么是程序替换?(原理?)
用新进程的代码和数据替换掉原来的代码和数据,其他都不变。
程序替换的本质:就是把程序的进程代码和数据加载到特定的进程上下文中(加载就需要加载器,它的底层原理可以理解成exec*程序替换函数(*可以理解成系列),进程程序替换会更改代码区的代码,这也会发生写时拷贝
进程的程序替换使用----怎么办?----阶段一
阶段二
各个程序替换函数的基本使用
命名理解
int execl(count char*path,count char *arg,...);
//path 就是要执行的文件全路径一定要是路径+文件名
// ... 意思是可变参数列表,要执行的目标程序在命令行上怎么执行这里就怎么一个一个传递进去()
//必须以NULL作为传入的参数的结束
int execv(const char* path,char *const argv[]);
//第一个参数还是传全路径
//第二个参数传的是一个数组,在命令行上怎么执行就将其存在数组中,然后传进去,要以NULL结尾
与execl除了传参的不同,其他没有区别
int execlp(const char* file,const char* arg,...);
//第一个参数是文件名,需要在环境变量当中才行,会自动去找寻地址
//后面的参数跟execl相同
int execvp(char* file,char *const argv[]);
//不用带路径会自动去环境变量中找,且通过数组传参
int execle(const char* path,const char *arg,...,char *const envp[]);
int execve(const char* path,char *const argv[],char *const envp[]);
用法同execle,唯一的区别就是它传的是数组
int execvpe(const char *file, char *const argv[],char *const envp[]);
跟上面的没有什么区别就是多了个p,可以参照之前的来看
为什么会有这么多接口?
是为了满足不同的应用场景