前言:
1 程序替换原理
2 单进程替换
3 替换函数
3.1 函数使用
4 程序去替换自己的另一个程序操作方式
5 实现自己的shell
通过我们之前对于子进程的应用,我相信大家一定是能够想到创建子进程的目的之一就是为了代劳父进程执行父进程的部分代码,也就是说本质上来说父子进程都是执行的同一个代码段的数据,在子进程修改数据的时候进行写时拷贝修改数据段的部分数据。
但是还有一个目的大家知道吗?不知道没关系,因为这就是本篇文章的主题,将子进程在运行时指向一个全新的程序代码,也就是我们的进程程序替换。
首先程序替换是将当前进程的全部数据被替换成为了另外的一个程序的代码再运行,那么这样做产生了一个什么样的效果?那就是我们没有必要对一个已经出现的程序再次的写一遍,以及不用在PCB数据结构当中再添加一个新的PCB块。如下图所示:
从上图当中我们可以知道一个进程的出现必须先在PCB结构当中先构建自己的PCB块,有了对应的虚拟地址之后,然后虚拟地址通过页表映射的方式在物理地址当中找到位置,然后将磁盘当中的程序加载进入对应的物理地址。
我们进程替换所做的事情就是不改变PCB的情况之下,后续的操作重新做了一遍,也就是说操作系统在这一块进行了全面的写实拷贝,连代码段的数据都被修改了。保证了代码的独立性。
只看上面的解释,我相信小伙伴们对于这一块还是不太理解,那么上代码:
7 int main()
8 {
9 printf("begin---\n");
10 execl("/bin/ls","ls","-a","-l",NULL);
11 printf("end----\n");
12 return 0;
13 }
我们可以看到,我们主程序当中写下了两个printf,分别在execl函数的前后,execl里面有我们熟悉的指令程序,也就是ls,先不管execl是什么,先观察效果。
发现了什么,我们在execl后面的printf没有被输出,也就证明了我们的程序替换是完全替换,会将我们的代码完全变为另外一个程序的代码。
#include
`
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[]);
由我们库封装的exec函数常用的有上面的几种,这些函数都有下面的特性,当该函数成功执行,那么进程替换成功,代码不再返回,如果函数调用失败,例如不正确的地址,不正确的文件等等,函数会返回一个-1,并且exec函数之后在函数调用失败时才有返回值,成功没有返回值。
只有失败时有返回值其实很好理解,因为函数调用成功那就表示程序替换成功,原来的代码都被替换了,我返回之后给谁?没了,之后的代码就是新代码了。
使用这些函数其实简单,先将函数名的exec提取出来看后面的几个字母。
l:表示用列表方式传递。
其中/bin/ls表示需要执行的文件是谁,ls表示执行方式,而-a和-l表示这个执行的参数列表。
v:表示使用数组的方式传递。
可以看到我们用过指针数组的方式将我们的执行和参数列表存到了一起,然后将这个指针数组作为参数传递给我们的execv函数就行。
p:表示自己只需要传递需要执行的文件是谁,操作系统会从默认环境变量当中去查找。
e:表示可以传递自己的环境变量。
注意:当我们传递自己的环境变量时会替换默认环境变量,所以如果想要添加一个环境变量,而不是替换那就需要下方的操作。
通过系统提供的存环境变量的environ变量,在用putenv函数添加自己的环境变量,以达到添加环境变量的操作。
上面的几个字母通过不同的组合可以达到不同的操作方式。
7 int main()
8 {
9 pid_t id = fork();
10 if(id == 0)
11 {
12 extern char** environ;
13 printf("begin+++++++++++++++++++++\n");
14 printf("begin+++++++++++++++++++++\n");
15 printf("begin+++++++++++++++++++++\n");
16 printf("begin+++++++++++++++++++++\n");
17 printf("我是子进程,PID是:%d\n",getpid());
18 char arg[] = "MYENV=You Can See Me";
19 putenv(arg);
//替换程序在exec目录下,执行文件名字是otherproc
20 execle("./exec/otherproc","otherproc",NULL,environ);
21 printf("end++++++++++++++++++++++\n");
22 printf("end++++++++++++++++++++++\n");
23 printf("end++++++++++++++++++++++\n");
24 printf("end++++++++++++++++++++++\n");
25 exit(1);
26 }
27
28 sleep(2);
29 int status = 0;
30 waitpid(id,&status,0);
31 printf("我是父进程,PID是:%d,子进程退出状态是:%d\n",getpid(),WEXITSTATUS(status));
32
33 return 0;
34 }
我们需要从原程序中替换到这个otherproc这个执行文件。
使用函数:execle("./exec/otherproc","otherproc",NULL,environ);
通过上面的学习相信大家对进程替换已经有了足够的了解了,那么大家有没有想过我们的Linux通过shell是怎么运行的呢?我们都知道我们创建的所有进程都是bash的子进程,那么我们是不是也可以写一个自己的bash,然后可以使用这些指令呢?答案是可以,请看下方代码。
1 #include
2 #include
3 #include
4 #include
5 #include
6 #include
7 #include
8
9 #define MAX_STR 1024
10 #define ARG 64
11 #define SPE " "
12
13 int splite(char commandstr[], char* argv[])
14 {
15 assert(commandstr);
16 assert(argv);
17 argv[0] = strtok(commandstr,SPE);
18 if(argv[0] == NULL) return -1;
19
20 int i = 1;
21 while((argv[i++] = strtok(NULL,SPE)));
22
23 return 0;
24 }
25
26 void show_command(char* argv[])
27 {
28 int i = 0;
29 while(argv[i] != NULL)
30 {
31 printf("%d,%s ",i,argv[i]);
32 i++;
33 }
34 printf("\n");
35 }
36
37 int main()
38 {
39 while(1)
40 {
41 //接收从界面接收的字符数组
42 char commandstr[MAX_STR] = {0};
43
44 //将我们的命令通过空格的方式切割成为一个一个的字符串
45 //第一个是我们的指令,后续的是参数列表
46 char* argv[ARG] = {NULL};
47
48 //我们的shell界面提示
49 printf("[zhangshan@mymachine test ]# ");
50
51 //因为我们希望直接看到上面的打印,而不是等缓冲区运行完毕
52 fflush(stdout);
53
54 //将输入的字符获取,并赋值给commandstr数组当中
55 char* s = fgets(commandstr,sizeof(commandstr),stdin);
56 assert(s);
57
58 //因为s这个变量我们并没有实际的使用,所以用一个不会做任何改变的操作使用
59 (void)s;
60
61 //我们输入指令时回安回车表示输入完毕,而我们不希望操作系统录入这个\n所以需要更改
62 commandstr[strlen(commandstr)-1] = '\0';
63
64 //将字符切割成为小字符串,然后赋值给argv这个指针数组当中
65 int n = splite(commandstr,argv);
66 show_command(argv);
67 //如果没有获取有效的命令,直接跳过本次循环,也就是指令失效
68 if(n != 0) continue;
69
70 //创建子进程,用于后续的程序替换
71 //因为程序替换需要完全替换后续的代码,也就是说如果用父进程去替换
72 //就不会有下一次的循环了,这不是我们期望的结果,所以建子进程
73 pid_t id = fork();
74
75 if(id == 0)
76 {
77 //通过有默认路径,查询目录加上指针数组的方式exec方式进行进程程序替换
78 execvp(argv[0],argv);
79 //替换之后是不应该被执行的,如果执行了则表示程序替换错误
80 exit(1);
81 }
82 int status = 0;
83
84 //只是回收子进程的退出信息,没有其余的操作,所以不用WNOHANG,也没有后续的代码
85 waitpid(id,&status,0);
86 }
87 }
该代码有很多细节,但是博主也不打算过多的解释了,因为博主已经在主程序的每一句话都写了注解,如果大家有兴趣可以自行阅读,难度不大。
运行结果:
以上就是博主对于进程替换的全部理解了,希望能够帮到大家。