进程程序替换+简易版shell实现

进程程序替换

什么是进程程序替换?
指在一个正在运行的进程中,将原来的程序替换成新的程序的过程。
eg:如果我们想让fork创建出来的子进程执行全新的任务,此时就需要进程程序替换
为什么要进程程序替换呢?
我们一般在服务器设计的时候(linux编程的时候)往往需要子进程干两种事情

  1. 让子进程执行父进程的代码片段(服务器代码)
  2. 让子进程执行磁盘中的一个全新的程序,eg:通过我们的进程执行其他人写的进程代码等等,可以使得我们的c/c++代码调用c/c++/Python/Shell/Java等代码

进程程序替换的原理
在这里插入图片描述
假设此时父进程执行的是a.exe,fork创建子进程之后,原本子进程要继承父进程的代码和数据,页表等映射也都是一样的,此时将磁盘中的b.exe加载如内存结构,使得子进程重新建立页表的映射关系,谁执行程序替换就重新建立谁的映射(此时是子进程)
效果:此时做到了让父子进程分离,并且让子进程执行一个全新的程序,至于如何做到,是有OS系统中的系统调用接口完成的。

如何进行程序替换

替换函数一共有六种,先用 execl第一种函数举例子
在这里插入图片描述
我们要执行一个全新的程序,需要做如下几件事情

  1. 先找到这个程序在哪里
  2. 程序可能携带选项进行执行,也可以不携带,(也就是程序怎么执行的)我们需要明确告诉OS,我想怎么执行这个程序,要不要带选项
  3. 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. 获取命令行
  2. 解析命令行
  3. 建立一个子进程
  4. 替换子进程
  5. 父进程等待子进程退出
    仅仅支持一些简单的命令
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 }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

每天少点debug

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值