1.进程退出的理论
进程退出有三个场景: ①代码运行完毕,结果正确 ②代码运行完毕,结果不正确 ③代码异常终止
main函数的返回值实际上是进程的退出码 echo $?指令可以输出最近一次程序退出时的退出码
后面的echo 为什么输出的是0? 以为最近的一次指令是上一次的echo,所以return 返回的是0 用退出码可以来判断程序执行的结果是正确还是不正确 我们一般用 0代表success , !0 代表failed 比如在这边错误的退出码就是2(2只是不正确情况中的一种,实际情况而定 ) 我们可以打印一下,看一下各种错误码 代码异常终止,在我们的VS中被称为程序崩溃,也就是说代码还没有跑完就终止了,在这种情况下,错误码是没有意义的了,因为是unknown的
2 .进程退出的方式
有三种方式
方式一:return
main函数return, 代表进程退出,非main函数的return叫做函数返回
方式二:exit
exit在任意地方调用,都叫做终止进程,参数是退出码 数据这边本身是会被放在缓冲区的,但是exit or main里面的return 本身就会要求系统进行,缓冲区刷新! 总结:return 和 exit 除了帮我们能够退出程序以外,还可以帮助我们刷新缓冲区
方式三:_exit
_exit终止进程,强制终止进程,不要进行后续的收尾工作,比如刷新缓冲区 一张图说明exit()和_exit的区别,_exit()直接终止,exit()要做以下的一系列工作 ps:不刷新缓冲区!=不释放系统资源
3 .进程退出,OS做了什么?
系统层面上来看:少了一个进程:free PCB, free mm_struct , free 页表和各种映射结构,代码+数据,申请的空间也要被杀掉
4.进程等待
原理
①进程wait是什么
父进程fork出来的子进程是为了帮父进程完成某种任务,父进程进行fork的时候,需要通过wait/waitpid等待子进程的退出
②为什么要让父进程等待
1 .通过获取子进程退出的信息,能够得知子进程执行结果 2 .可以保证:时序问题,子进程先退出,父进程再退出 3 .子进程退出的时候会进入僵尸状态,会造成内存泄漏的问题,需要通过父进程wait,释放该子进程占用的资源
③怎么进行进程等待(wait,waitpid)
wait 不想让子进程完成之后还执行父进程的代码,子进程可能要执行五秒,父进程早就退出了,子进程会被系统领养,变成孤儿进程;然而,我们不想这么干,我们要等待子进程
我们可以看到刚开始程序有两个进程,慢慢地子进程没有被父进程回收变成了僵尸进程 接着父进程开始等待,回收子进程 最后父进程也退出程序结束 说明:我们的wait是可以回收僵尸进程的
waitpid 这里第一个参数指定为id的话等待的就是id 这里第一个参数如果是-1的话,表示的是等待任意 一个 子进程,等价于刚刚的wait 这里的status是输出型参数 status的构成:有32个比特位,只使用低16位比特位,高16位比特位不用
父进程拿到什么status结果,一定和子进程如何退出强相关!!! 子进程退出的话题,不就是我们刚刚将的进程退出吗?
最终一定要让父进程通过status得到子进程执行的结果 执行的结果就是之前讲的三种 结果正确和不正确,也就是看退出码,也就是return or exit的结果 如何判断代码异常终止?本质是这个进程因为异常问题,导致自己收到了某种信号
实操
①获取子进程的退出结果(右移方式)
再稍微修改一下,就是我们代码正确的情况
bash是命令行启动的所有进程的父进程,bash一定是通过wait方式得到子进程的退出结果,所以我们能看到echo $?能够查到子进程的退出码!
②接口方式
status: WIEXITED(status):查看进程是否是否正常退出 WEXITSTATUS(status):若WIFEXITED非0,提取子进程退出码(查看进程退出码)
③WNOHANG
第三个参数OPTIONS: WNOHANG:设置等待非阻塞 首先理解阻塞和非阻塞:
阻塞等待:现实生活中,等一个朋友的期间什么都没有干,没等到就不回家(不挂电话,朋友一直在电话中等)
非阻塞等待:现实生活中,不停的打电话挂电话询问,检测朋友的运行状态 可能需要多次检测:基于非阻塞状态等待的轮询方案
OS层面上看 阻塞的本质:其实是进程的PCB被放入了等待队列,并将进程的状态改为S状态 返回的本质:进程的PCB从等待队列拿到R队列,从而被CPU调度
无论是阻塞非阻塞,都是等待的一种。谁等?等谁?等什么? 现在这种情况是父进程在等子进程退出
5.进程的程序替换
①什么是程序替换
进程不变,仅仅替换当前进程的代码和数据的技术,叫做,进程的程序替换
相当于我们用了一个老的进程的壳子,执行了新的代码和数据 有没有创建新的进程?没有 我们发现后面打印的语句竟然被替换了 为什么后面的代码没有执行??因为程序替换,后面的代码和数据全都被替换掉了 程序替换的本质就是 把指定的代码+数据,加载进特定进程的上下文中!! 子进程进行代码替换,父进程间断持续打印,最后阻塞回收 但是为什么子进程在执行的时候,父进程也在执行呢??? 答案是进程具有独立性 之前所说的父子进程代码共享是建立在没有进程程序替换的前提下的, 也就是说, 进程程序替换会更改代码区的代码,也要发生写时拷贝
联想前面,我们创建子进程的目的,是为了让子进程执行父进程代码的一部分!那么如果想让子进程执行一个“全新的程序”呢? 程序替换!
只要exec*的函数返回了,就一定是因为调用失败了,不然一定不会使用后续的代码的?
②为什么要执行程序替换
目的也就是让子进程去执行一个全新的程序!
③各个程序替换函数的基本使用(execl,p,v,ve,le)
使用man execl查看使用 先写一个测试代码 我们发现程序替换之后的代码end<<也没有了 也可以知道execl返回的话函数必定出错了 为什么出来有execl , 还有execlv , execlv这些呢???? 命名理解
l ( list ) : 表示参数采用列表 这样一个一个传入就叫列表
v ( vector ) : 参数用数组
p ( path ): 有p自动搜索环境变量PATH 只要传file名帮我们自动寻找 vp也就是v+p的功能同理
e (env) : 表示自己维护环境变量
我们想要通过执行myexe.c然后把我们的myload.c给跑起来 把myexec.c全部替换成myload.c的过程
Makefile默认只会执行第一个依赖关系,所以如果我们想要同时编译两个文件,就需要写成下面的这种方式 想执行多个执行文件只需要在all的后面往后跟就可以了 我们成功地通过运行myload将myexe的内容执行了 验证execle 也就是说,带e的可以自定义环境变量 这样来看 execve也很简单的 适当修改代码,检验同样也是可以的 总结:以后写程序如果有其他语言写的代码,我们可以通过exec进行接口调用 这里用c++程序调用python代码举个例子 为什么会有这么多的接口? 是为了满足不同的应用场景的,但是万变不离其宗
④制作一个简单的shell
什么是解释器? 解释器的本质不就是给你一个命令窗口,然后不断的获取你的输入吗?
如何获得你的输入? C语言中的strtok()? 我们的“破烂版”mini_shell
1 #include<stdio.h>
2 #include<unistd.h>
3 #include<stdlib.h>
4 #include<sys/wait.h>
5 #include<string.h>
6
7 #define NUM 128
8 #define CMD_NUM 64//最多可以定义64个字符
9
10 int main()
11 {
12 char command[NUM];
13 for(;;)
14 {
15 //步骤一:打印提示符
16 char *argv[CMD_NUM] = {NULL};//这是一个指针数组,可以用来拆开 ls -l -a 的每个选项
17 command[0]=0;//这种方式可以做到O(1)的复杂度清空字符串
18 printf("[super_zkx@myhostname minishell]#");
19 fflush(stdout);
20
21 //步骤二:获取字符串
22 fgets(command,NUM,stdin);
23 //输入的时候会多一个回车
24 command[strlen(command)-1] ='\0';//"ls\n\0" "\n"算的是一个字符,strlen算出来是3 - 1 = 2 \n对应下标刚好是2
25
26 //步骤三:解析字符串
27 // printf("echo: %s\n",command);
28 const char* sep = " ";
29 argv[0] = strtok(command,sep);//传入字符串,以sep为分隔符
30 int i = 1;
W> 31 while(argv[i] = strtok(NULL,sep))
32 {
33 i++;
34 }
35
36 //步骤四:检测命令是否需要shell本身执行的内建命令
37 if(strcmp("cd",argv[0]) == 0){
38 if(argv[1] != NULL) chdir(argv[1]);
39 continue;
40 }
41
42
43 //步骤五:执行第三方命令
44 if(fork() == 0)
45 {
46 //child
47 execvp(argv[0],argv);
48 //如果返回了就说明失败了,那么我们就终止进程
49 exit(1);
50 }
51
52 int status = 0;
53 waitpid(-1,&status,0);
54 printf("exit code: %d\n",(status >> 8)&0xFF);
55 // while()
56 // {
57 // //如果是老串的话,第一个变量就传NULL
58 // argv[i] = strtok(NULL,sep);
59 // if(argv[i] == NULL)
60 // break;
61 // i++;
62 // }
63 // for(i = 0; argv[i];i++)
64 // {
65 // printf("argv[%d]:%s\n",i,argv[i]);
66 // }
67
68 }
69 return 0;
70 }
|