索引
- 进程程序替换
- 如何进行程序替换
- 不同程序替换函数之间的区别
- 系统接口调用其他语言的函数
- 替换函数execle
- 简易版shell实现
进程程序替换
什么是进程程序替换?
指在一个正在运行的进程中,将原来的程序替换成新的程序的过程。
eg:如果我们想让fork创建出来的子进程执行全新的任务,此时就需要进程程序替换
为什么要进程程序替换呢?
我们一般在服务器设计的时候(linux编程的时候)往往需要子进程干两种事情
- 让子进程执行父进程的代码片段(服务器代码)
- 让子进程执行磁盘中的一个全新的程序,eg:通过我们的进程执行其他人写的进程代码等等,可以使得我们的c/c++代码调用c/c++/Python/Shell/Java等代码
进程程序替换的原理
假设此时父进程执行的是a.exe,fork创建子进程之后,原本子进程要继承父进程的代码和数据,页表等映射也都是一样的,此时将磁盘中的b.exe加载如内存结构,使得子进程重新建立页表的映射关系,谁执行程序替换就重新建立谁的映射(此时是子进程)
效果:此时做到了让父子进程分离,并且让子进程执行一个全新的程序,至于如何做到,是有OS系统中的系统调用接口完成的。
如何进行程序替换
替换函数一共有六种,先用 execl第一种函数举例子
我们要执行一个全新的程序,需要做如下几件事情
- 先找到这个程序在哪里
- 程序可能携带选项进行执行,也可以不携带,(也就是程序怎么执行的)我们需要明确告诉OS,我想怎么执行这个程序,要不要带选项
- eg:命令行怎么写ls -l -a 第二个参数就怎么填 “ls”, “-l”, "-a"并且参数的最后一定是NULL,表示【如何执行程序】参数传递完毕
发现此时执行了第一个printf,然后执行了ls命令,但是第二个printf没有打印了,为什么?
一旦替换成功,是将当前进程的代码和数据全部替换,后面的printf是原来进程的代码,所以改代码早就被替换了,改代码不存在了。
所以该程序替换函数需要返回值吗?
int ret = execl(…)
一旦替换成功,就会执行新的进程代码,就不会有返回值
而失败的话,必然会顺着原来的进程代码执行,最多通过返回值得到是什么原因导致的替换失败!
引入子进程创建
子进程执行程序替换,会影响父进程吗?
不会,因为进程具有独立性
如何做到的?
数据层面发生写实拷贝,程序替换的时候,我们可以理解为,代码和数据都发生了写实拷贝完成父子的分离。
19 printf("我是父进程,我的PID是:%d\n",getpid());
20 pid_t id = fork();
21 if(id == 0) {
22 //我们想让子进程执行全新的程序,以前是执行父进程的代码片段
23 printf("我是子进程,我的PID是:%d\n", getpid());
24 execl("/usr/bin/ls","ls","-a","-l",NULL);
25 printf("子进程替换进程失败\n");
26 exit(1);//只要执行了exit,意味着,execl系列的函数失败了
27
28 }
29 //一定是父进程
30 int status = 0;
31 printf("我是父进程,我的PID: %d,我准备等子进程啦!\n",getpid());
32 int ret = waitpid(id,&status,0);//阻塞式等待子进程
33 if(ret == id) {
34 sleep(1);
35 printf("父进程等待成功!子进程的退出码是: %d \n",(status>>8)&0XFF);
36 }
37 return 0;
38 }
不同程序替换函数之间的区别
int execl(const char *path, const char *arg, …);
int execv(const char *path, char *const argv[]);
execv VS execl
二者几乎是一样的,只有传参方式的区别,execl传参的方式是list列表传参,execv是vector数组传参
int execvp(const char *file, char *const argv[]);
函数名中有p和v表示使用该函数时,可以不带路径,并且函数第二个参数传参的时候是vector传参
系统接口调用其他语言的函数
调用c++函数
调用Python
替换函数execle
int execle(const char *path, const char *arg, …,char *const envp[]);
函数名中的e表示环境变量
第三个参数既可以自己传自定义的环境变量
也可以传系统定义的环境变量,二者有所区分
上述代码中:mycomd.cc调用了getenv函数,打印出环境变量,而我们在mtproc.cc中先用execl调用mycomd,发现此时只有环境变量PATH那一行代码被打印出来了,到MYPATH这行代码时,就无法再运行下去了,因为此时在系统中没有环境变量MYPATH。
如图所示,如果我们导出环境变量变量的话,此时发现都可以打印出来了。
但是如果我们用execle函数自定义导入环境变量的话。
如果此时导入自定义变量,发现PATH系统自带的环境变量打印不出,说明添加环境变量给目标进程,是覆盖式的,只要传入自定义的环境变量,那么原来的环境变量,就失去作用了。
如果execle传入的是系统定义的环境变量,那么我们export导入的环境变量还是有用的。
问题:为什么程序替换会有那么多借口?
为了适配更多的应用场景
execve为什么是单独的?
int execve(const char *path, char *const argv[], char *const envp[]);
只有这个函数是系统接口,上面的函数都是对这个函数的封装
总结:
对于替换函数
l(list) : 表示参数采用列表
v(vector) : 参数用数组
p(path) : 有p自动搜索环境变量PATH
e(env) : 表示自己维护环境变量
简易版shell实现
shell的大致原理,以ls为例
shell从用户读入字符串,shell建立一个新的进程,然后在那个进程中运行ls程序并等待ls进程结束,然后shell读取新的一行输入,建立一个新进程,在这个进程中运行程序,并等待这个进程结束。所以要写一个shell,需要循环以下过程:
- 获取命令行
- 解析命令行
- 建立一个子进程
- 替换子进程
- 父进程等待子进程退出
仅仅支持一些简单的命令
1 #include<stdio.h>
2 #include<string.h>
3 #include<stdlib.h>
4 #include<unistd.h>
5 #include<sys/types.h>
6 #include<sys/wait.h>
7
8 #define SEP " "
9 #define NUM 1024
10 #define SIZE 128
11 char command_line[NUM];
12 char *command_args[SIZE];
13 char env_buffer[NUM];//fou test
14 extern char*enviorn;
15
16 int ChangDir(const char * new_path)
17 {
18 chdir(new_path);
19 return 0;//成功
20
21 }
22 void putEnvInMyShell(char *new_env)
23 {
24 putenv(new_env);
25 }
26 int main()
27 {
28 //shell本质上就是一个死循环
29 while(1)
30 {
31 //1.显示提示符
32 printf("[zjt@大帅比 当前目录]# ");
33 fflush(stdout);//强制刷新屏幕,否则上述信息回储存在缓冲区中
34 //2.获取用户输入
35 memset(command_line,'\0',sizeof(command_line)*sizeof(char));
36 fgets(command_line,NUM,stdin);//键盘,标准输入,stdin,获取的是c风格的字符串,'\0';
37 command_line[strlen(command_line)-1] = '\0';//清空'\n'
38 //3."ls -a -l -i" -> "ls" "-a" "-l" "-i"字符串切分
39 command_args[0] = strtok(command_line,SEP);
40 int index = 1;
41 //给ls命令添加颜色
42 if(strcmp(command_args[0]/*程序名*/,"ls") == 0)
43 command_args[index++] = (char*)"--color=auto";
44 //strtok截取成功,返回字符串起始地址
45 //截取失败,返回NULL;
46 while(command_args[index++] = strtok(NULL,SEP));
47 //按照SEP进行字符串切割,第一次调用时,第一个参数指向要分割的字符串
48 //以后每次调用第一个参数都指向NULL,表示继续分割上一次的剩余部分
49 //最终根据分割字符分割字符串
50 //TODO,编写后面的逻辑,内建命令
51 if(strcmp(command_args[0], "cd") == 0 && command_args[1] != NULL)
52 {
53 ChangDir(command_args[1]);//让调用方进行路径切换,父进程
54 continue;
55
56 }
57 if(strcmp(command_args[0], "export") == 0 && command_args[1] != NULL)
58 {
59 //目前,环境变量信息在command_line,会被清空
60 //此处我们需要保存一下环境变量内容
61 strcpy(env_buffer,command_args[1]);
62 putEnvInMyShell(env_buffer);
63 continue;
64
65 }
66 //5.创建进程,执行
67 pid_t id = fork();
68 if(id == 0)
69 {
70 //child
71 //6.程序替换
72 execvp(command_args[0],/*我们保存的要执行的程序名*/command_args);
73 exit(1);
74 //此时子进程调用一定失败
75 }
76 int status = 0;
W> 77 pid_t ret = waitpid(id, &status, 0);
78 // if(ret > 0)
79 // {
80 // printf("子进程等待成功,进程退出码为:%d,退出信号为:%d\n",(status>>8)&0xFF,status&0xFF);
81 //
82 // }
83 }
84 }