Linux进程替换(实现一个简单的shell)

举报
卖寂寞的小男孩 发表于 2022/10/15 10:10:35 2022/10/15
【摘要】 本文主要介绍Linux进程替换,并使用Linux进程替换实现一个简单的shell

@[toc]

零.前言

本文将详解进程替换以及实现进程替换的七个函数,并通过进程替换实现一个简单的shell。

一、进程替换

✔1.进程替换的概念

进程替换就是指将当前程序替换成一个新的程序,让当前进程执行这个新程序,如果替换成功了,原来的程序就不会被执行,也不会返回原来的返回值。

✔2.进程替换的原因

进程替换通常是在子进程中完成的,在学习进程替换之前,我们都是通过条件判断fork()的返回值使用子进程来执行父进程的一部分的代码,其实意义并不大。学习了进程替换可以使子进程执行一个全新的程序。

✔3.进程替换的本质

一个进程包含PCB,mm_struct(虚拟内存)等结构体,用来联系虚拟内存与物理内存的页表,以及程序的代码和数据。当进程中发生进程替换时,只有代码和数据发生替换,其他的内容不变。也就是说进程替换没有创建新的进程。

✔4.进程替换的方法

💌(1)函数的概述

进程运行起来时,它的代码和数据是要被加载到内存中的,是通过exec系列的函数来完成加载的。exec系列函数一共有七个,我们可以通过man手册来进行查询:
在这里插入图片描述
通过观察这几个函数我们发现它们都是在exec后面加入了几种后缀名。
可以通过后缀名的含义来进行区分记忆。

l:表示参数采用列表(以列表的方式一个一个传入进去)。
v:参数用数组。
p:有p自动搜索环境变量PATH(只要说名字就可以了,不用指明路径)。
e(env):表示自己维护的环境变量。(不用默认的环境变量)。

💌(2)execl

以l为后缀,说明以列表的形式传入参数。

int execl(const char *path, const char *arg, ...);

其中*path表示的是路径,arg表示的是要执行的程序,“…”表示的就是可变参数列表,即命令行上怎么执行这里就写入什么参数。必须以NULL作为参数列表的结束。

#include<unistd.h>    
#include<stdio.h>    
#include<sys/wait.h>    
#include<stdlib.h>    
int main()    
{    
  if(fork()==0)    
  {    
    printf("command begin\n");    
    execl("/usr/bin/ls","ls","-a","-l",NULL);                                                                                                            
    printf("command fail\n");    
    exit(1);    
  }    
  waitpid(-1,NULL,0);    
  printf("wait child success\n");    
  return 0;    
}    

当子进程执行完打印command begin的语句的时候,进行进程的替换。其中替换的是/usr/bin/ls,在命令行要输入的是ls -a -l,将程序运行起来:
在这里插入图片描述
我们发现子进程被替换为了ls进程,并添加了-a -l等选项。

💌(3)execv

execv的后缀是v,指的是以数组的形式输出参数。
只需要将上面的execl替换为如下即可。

      char* argv[]={"ls","-l","-a",NULL};    
      execv("/usr/bin/ls",argv);

其本质就是将参数列表放在了一个数组argv中。

💌(4)execlp

后缀为l和p,l表示的是以参数列表的形式,p表示的是不用指明具体的路径,会根据环境变量去查找文件。

    execlp("ls","ls","-l","-a",NULL);    

就是将本来要指定的路径改成了通过环境变量进行查找。

💌(5)execvp

后缀为v和p,v表示以数组的形式,p表示的是不指明具体的路径去查找文件:

      char* argv[]={"ls","-l","-a",NULL};    
      execvp("ls",argv);   

💌(6)execle

后缀为l和e,e表示自己维护环境变量,不使用默认的环境变量。即向要执行的程序中导入环境变量:
建立两个程序一个程序完成进程替换,另一个程序为被替换的程序,负责打印自己的环境变量:

//mytest.c用来打印自身的环境变量
#include<stdio.h>    
#include<stdlib.h>    
#include<unistd.h>    
int main()    
{    
  extern char** environ;    
  int i;                                                                                                                                                 
  for(i=0;environ[i];i++)    
  {    
    printf("%d:%s\n",i,environ[i]);    
  }    
}   
//在myload.c中进行进程替换
      char* env[]={"MYENV1=hahaha","MYENV2=hehehe",NULL};    
      execle("./mytest","mytest",NULL,env);   

这里要注意的是已经指明了路径,在可变参数列表中可以不写入./
当我们不使用进程替换的时候,直接运行mytest,生成的是系统默认的环境变量:
在这里插入图片描述
如果通过进程替换执行mytest的话,它的环境变量就被定义成了env[],运行的结果是:
在这里插入图片描述

💌(7)execve

execve的代码同理:

      char* argv[]={"mytest",NULL};                                                                                                                      
      char* env[]={"MYENV1=hahaha","MYENV2=hehehe",NULL};    
      execve("./mytest",argv,env);   

💌(8)execvpe

    char* argv[]={"mytest",NULL};    
    char* env[]={"MYENV1=hahaha","MYENV2=hehehe",NULL};    
      execvpe("mytest",argv,env); 

由于p是在环境变量中找文件,那么就需要将我们新创建的文件添加到环境变量中,添加的方法为:

export PATH=$PATH:/home/用户名

此时再来执行myload得到我们希望获得的结果:
在这里插入图片描述

💌(9)为什么有这么多函数?

其实所有的接口本质上无差别就是传递的参数不同而已,是为了满足不同的应用场景进行的封装。
操作系统实际上只提供了一个接口那就是:execve,其他的函数都是对该接口封装而成的库函数。它们的底层都是使用execve来进行实现的。

二、实现一个简单的shell

既然我们已经了解了进程替换,那么我们就可以通过进程替换来调用命令行进程从而实现一个DIY的shell。

🔶1.实现命令行输入

首先它一定是一个死循环的程序,因为shell在不停地等待我们输入内容。

    char command[NUM];    
    command[0]=0;  //定义commmand接收命令行信息    
    printf("[lhb@myhostname mydir]# ");                                                                                                                  
    fgets(command,NUM,stdin);//在标准输入流(键盘)读取输入的信息存入command中    
    command[strlen(command)-1]='\0'; //由于将回车读入了进去,因此需要将回车部分置为'\0'   
  }    

此时可以测试一下效果:
在这里插入图片描述

🔶2.将command中内容分割

命令和每一个选项之间是用" "隔开的。因此通过查找空格的位置可以切割command字符串,从而找到每一个命令和选项。

      char* argv[CMDNUM]={NULL};    
      argv[0]=strtok(command," ");    
      int i=1;    
      while(argv[i]=strtok(NULL," "))    
      {    
        i++;    
      }   

这里涉及到C语言函数分割字符串strtok的使用,忘记的小伙伴可以查一查呀。

🔶3.通过程序替换执行命令

通过execvp函数进行进程替换即可:

      if(fork()==0)    
      {                                                                                                 
        execvp(argv[0],argv);                                                                           
        exit(1);                                                                                        
      }                                                                                                 
      waitpid(-1,NULL,0);    

可以看到效果:
在这里插入图片描述

🔶4.处理内建命令

当我们执行ls时,是可以正常运行的,但是当我们执行cd命令的时候会发生错误:
在这里插入图片描述
我们发现当执行cd命令的时候,路径并没有发生改变。
这是因为cd是子进程执行的,当该子进程执行完cd路径退回后,子进程执行结束了,但是没有改变父进程的路径。所以当父进程再次创建子进程执行pwd时,路径还是之前的路径。C语言提供了chdir函数来改变当前工作路径。
当输入的命令是cd的时候,切换路径,并直接执行下一次循环。

        if(strcmp(argv[0],"cd")==0)                                                                                                                      
        {    
          if(argv[1]!=NULL)   //当argv[1]不为空时切换路径为argv[1] 
           chdir(argv[1]);    
          continue;    
        }   

🔶5.获取子进程的pid

可以添加一些功能比如获取子进程的pid等等:

      int status=0;    
      waitpid(-1,&status,0);    
      printf("exit code:%d\n",(status>>8&0xFF));      

在这里插入图片描述

三、总结

进程替换一般应用在子进程,这样可以使子进程执行完全不同于父进程的代码,而自己实现shell的本质就是将shell作为父进程而命令行执行的程序作为子进程,对子进程的命令进行替换从而实现一个简单的shell。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。