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


    零.前言

    本文将详解进程替换以及实现进程替换的七个函数,并通过进程替换实现一个简单的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, ...);
    
    • 1

    其中*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;    
    }    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    当子进程执行完打印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);
    
    • 1
    • 2

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

    💌(4)execlp

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

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

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

    💌(5)execvp

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

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

    💌(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]);    
      }    
    }   
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    //在myload.c中进行进程替换
          char* env[]={"MYENV1=hahaha","MYENV2=hehehe",NULL};    
          execle("./mytest","mytest",NULL,env);   
    
    • 1
    • 2
    • 3

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

    💌(7)execve

    execve的代码同理:

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

    💌(8)execvpe

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

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

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

    此时再来执行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'   
      }    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

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

    🔶2.将command中内容分割

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

          char* argv[CMDNUM]={NULL};    
          argv[0]=strtok(command," ");    
          int i=1;    
          while(argv[i]=strtok(NULL," "))    
          {    
            i++;    
          }   
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

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

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

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

          if(fork()==0)    
          {                                                                                                 
            execvp(argv[0],argv);                                                                           
            exit(1);                                                                                        
          }                                                                                                 
          waitpid(-1,NULL,0);    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

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

    🔶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;    
            }   
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    🔶5.获取子进程的pid

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

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

    在这里插入图片描述

    三、总结

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

  • 相关阅读:
    上海交通大学计算机考研资料汇总
    机器学习的初学术语掌握
    Intel汇编-函数使用全局变量传递数据
    【毕业设计】基于大数据的招聘职业爬取与分析可视化
    c++(25)STL:类型转换、异常机制
    Linux 日志系统、auditd用户审计、kdump故障定位
    在playfab server上部署unreal linux server
    【C语言 模拟实现strcmp函数】
    汇编层面有三个主要的操作对象
    python 学习笔记(1)—— 基础介绍
  • 原文地址:https://blog.csdn.net/qq_51492202/article/details/125201757