进程终止
一个进程退出了,无非只有三种情况:
- 代码跑完了,结果正确
- 代码跑完了,结果不正确
- 代码没跑完,程序异常退出了
代码跑完了,我们可以通过退出码获取其结果是否正确,(这个退出码就是我们在main函数都是写上 return 0)。而进程没跑完,就出现异常退出了,那么退出码就是无意义的。
进程退出码
是什么:OS用来表示一个进程结束时候状态的一个数值。
规定:0表示代码正确,非0表示出错。那么退出的信息谁来读取?父进程读取进程的退出码。
查看:通过echo $? 查看进程的退出码(注意:这次最近一次进程退出的结果)
在C语言中,sterror函数会提供特定的错误码信息
同时会提供一个全局的变量errno用于记录特地函数出错的结果,称为错误码。
这个全局码可以被strerror函数解析处错误的信息。
errno会记录最后一次函数的出错信息,可以手动清零。
许多系统调用出错,就会设置errno。比如将read设置为非阻塞,如果读出错,可能真的出错了,也可能本轮没有读到数据,就可以通过errno判断是否为EAGAIN。
错误码VS退出码
- 错误码就是函数出错的原因,一般不会直接影响进程的结果。
- 退出码用来衡量进程退出的结果。
进程退出的方式
正常终止
- return
- exit()
- _exit()
异常退出
- 信号终止
关于 exit和_exit的区别
exit是C语言提供的库函数,底层封装了系统调用。会引起一个进程正常退出,并且会将缓冲区刷新。而_exit是系统调用,也是引起进程正确退出,但不会将缓冲区刷新。
演示区别
本质的区别就是数据被保留在缓冲区中了,没有被刷新到外设上。
进程终止,内核做了什么?
进程=内核数据结构+进程代码 和 数据
进程终止后,释放代码+数据资源,OS会先将进程的状态修改会Z状态,等待父进程读取进程的退出信息。
随后将状态由Z修改为X,并且释放内核数据结构。
进程等待
什么是进程等待:
通过wait或者waitpid,(父进程)对子进程的资源进行回收的等待过程。
为什么进程等待:
1.解决僵尸问题带来的内存泄漏
2.必要时候,获取子进程的退出信息(退出码+退出信号)
如何等待:
- wait()
通过系统调用函数,回收指定进程的资源。
返回值:成功返回等待成功的子进程,失败返回-1。
其中wait的参数为整形指针,用于接收退出信息。
演示:故意让子进程先退出,父进程再等待回收。
会看到子进程退出后,状态变为Z状态,随后立马被删除,也就是被父进程回收资源了
- waitpid
pid_t waitpid(pid_t pid, int *_Nullable wstatus, int options);
waitpid常用于等待任意的进程,关于参数:
- pid:指定的进程,-1表示等待任意进程。
- wstatus:输出型参数,获取子进程的退出结果(退出码和退出信号)
- options选项:(常常设置为0表示阻塞等待,也可以设置为WNOHANG表示非阻塞等待)
获取退出信息
退出信息是一个16位的字段
0-7位表示终止信号,8位标识核心转储,9-15表示退出码
演示获取退出信息
1 #include<stdio.h>
2 #include<sys/wait.h>
3 #include<stdlib.h>
4 #include<unistd.h>
5 int main(){
6 pid_t rid=fork();
7 if(rid==0){
8 //子进程
9 printf("我是子进程,我要退出了\n");
10 exit(123);
11 }
12
13 //父进程等待回收
14 int code=0;
15 pid_t id=waitpid(-1,&code,0);
16 //显示退出的信息
17 printf("父进程等待成功,子进程的退出信息,exit_code:%d,sig:%d\n", (code>>8)&0xFF,code&0x7F);
18 return 0;
19 }
通过信号将子进程终止,父进程也能读取到
但是这样退出需要程序员自己做位运算,比较麻烦,库函数提供宏,获取退出退出信息。
- WIFEXITED:退出码
- WEXITSTATUS:信号
这里就不做演示了,用法比较简单。
父进程如何得知子进程退出信息
引出一个共识:硬件设备有它的等待队列,而PCB内也会内置等待队列。
所以这就是父进程调用wait阻塞的原因。
父进程等待多个子进程
如果没有保存frok之后的id,一般都是通过waitpid(-1)一次性等待。
非阻塞等待子进
通过waitpid的op选项字段,设置为非阻塞等待
如果rid>0等待成功,rid==0无进程回收,rid<0表示出错
程序替换
程序替换常用于父子进程分离、父进程调用其它语言、进程间的协作等。
见一见进程替换
调用exel函数,替换系统进程 ls
程序替换的原理
- 替换进程的代码+数据(包括堆栈数据),替换后的新进程从main函数开始执行。
- OS对页表的重新映射,保证新程序的代码+数据会被正确映射到物理内存上。
- 原本子进程可能和父进程共享代码,现在会重新申请物理内存,存放新进程的代码。实现父子进程的分离。
程序替换的方法
必须找到程序的替换的可执行进程
指定替换的方法
认识程序替换函数
C语言为程序替换提供6个函数,以满足不同的场景需求。
实际上这些函数换汤不换药,用法基本一致。
exec表示执行
- l:list 参数以列表的方式传递(命令行上怎么写,这里就怎么写),比如"ls" "-a " "-l"
- p:指定可执行程序的文件名,会自动到PATH中寻找
- v:对应的是l,利用 char * argv[ ]数组,将参数传递
- e:是环境变量表
演示以数组参数
程序替换我们自己的进程
利用C语言调用C++
注意:
环境变量被子进程继承是默认的行为,为什么?
程序替换只替换代码+数据,不替换地址空间中的命令行参数和环境变量表。
当我们执行可执行程序的时候,OS为我们做的就是创建PCB,加载代码+数据。
而这个加载的操作就是程序替换。