进程间程序替换和minishell

进程间程序替换和minishell

一、进程间程序替换的原理:

1.用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),这样就达不到创建进程的目的。

2.子进程往往要调用一种exec函数以执行另一个程序。当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。

3.调用exec并不创建新进程,只是把进程的数据和代码替换一下,让其执行别的逻辑,所以调用exec前后该进程的pid并未改变。
在这里插入图片描述
如图所示,有一个进程创建出的子进程本来是拷贝父进程的PCB,如果不进行分流,那么父子进程将会执行一摸一样的逻辑,所以现在用exec函数族把子进程的数据段和代码段替换掉,让其执行别的逻辑

二、exec函数族的用法及

1.execl函数

int execl(const char *path, const char *arg, ...);
参数 解释
const char *path 带路径的可执行程序(“/usr/bin/ls”)
const char *arg 给可执行程序传递的参数,但必须是以可执行程序名作为第一个参数(ls -a -l)
可变参数列表,必须以NULL结尾,作为终止符

2.execlp函数

int execlp(const char *file, const char *arg, ...);
参数 解释
const char *file 可执行程序的名称,但是注意该可执行程序必须在PATH环境变量中可以被找到,否者就会替换失败
const char *arg 给可执行程序传递的参数,但必须是以可执行程序名作为第一个参数(ls -a -l)
可变参数列表,必须以NULL结尾,作为终止符

总结一下exec函数族中代p和不带p的区别:如果带p,说明进程运行时会去环境变量中寻找,第一个参数直接传递可执行程序名即可。对于不带p,第一个参数必须指明可执行程序的路径。

3.execle函数

int execle(const char *path, const char *arg, ...,char *const envp[]);
参数 解释
const char *path 带路径的可执行程序
const char *arg 给可执行程序传递的参数,但必须是以可执行程序名作为第一个参数(ls -a -l)
可变参数列表,必须以NULL结尾,作为终止符
char *const envp[] 程序员自己组建环境变量,如果没有则认为当前进程没有环境变量,注意⚠️环境变量也是以NULL结尾的

4.execv函数
总结一下带e和不带e的区别:如果函数带e表示需要自己组装环境变量。不带e则不需要自己组装环境变量

int execv(const char *path, char *const argv[]);
参数 解释
const char *path 带路径的可执行程序
const char *argv[] 给可执行程序传递的参数,但必须是以可执行程序名作为第一个参数,以NULL结尾

5.execvp函数

int execvp(const char *file, char *const argv[]);
参数 解释
const char *file 可执行程序名,不带路径
char *const argv[] 给可执行程序传递的参数,但必须是以可执行程序名作为第一个参数,以NULL结尾

6.execve函数

int execve(const char *path, char *const argv[], char *const envp[]);
参数 解释
const char *path 带路径的可执行程序名
char *const argv[] 给可执行程序传递的参数,但必须是以可执行程序名作为第一个参数,以NULL结尾
char *const envp[] 程序员自己组建环境变量,如果没有则认为当前进程没有环境变量,注意⚠️环境变量也是以NULL结尾的

用下面的方法方便理解记忆:

  1. l(list) : 表示参数采用列表
  2. v(vector) : 参数用数组
  3. p(path) : 有p自动搜索环境变量PATH
  4. e(env) : 表示自己维护环境变量
#include <unistd.h>
int main()
{
char *const argv[] = {"ps", "-ef", NULL};
char *const envp[] = {"PATH=/bin:/usr/bin", "TERM=console", NULL};
execl("/bin/ps", "ps", "-ef", NULL);
// 带p的,可以使用环境变量PATH,无需写全路径
execlp("ps", "ps", "-ef", NULL);
// 带e的,需要自己组装环境变量
execle("ps", "ps", "-ef", NULL, envp);
execv("/bin/ps", argv);
// 带p的,可以使用环境变量PATH,无需写全路径
execvp("ps", argv);
// 带e的,需要自己组装环境变量
execve("/bin/ps", argv, envp);
exit(0);
}

三、它们之间的联系

1.联系:
在这里插入图片描述
从上图可以看到所有的库函数exec底层都是调用系统调用函数exeve函数

2.对于它们返回值的理解:

  1. 这些函数如果调用成功则加载新的程序从启动代码开始执行,不再返回。
  2. 如果调用出错则返回-1。
  3. 所以exec函数只有出错的返回值而没有成功的返回值

四、简易的shell

#include <stdio.h>
#include <sys/wait.h>
#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>

char g_command[1024];

//该函数的功能是获取命令行输入的数据
int GetCommand()
{
  //因为开始获得的内存中的内容是不定的,所以使用前要先初始化
  memset(g_command,'\0',sizeof(g_command));
  //这里需要为'\0'留一个位置,不能把g_command全部用来读取内容,否者就没有结束标志,容易发生内存访问越界
  if(fgets(g_command,sizeof(g_command)-1,stdin)==NULL)
  {
    printf("fgets is error!\n");
    return -1;
  }
  //printf("g_command:%s\n",g_command);
  return 0;
}

//解析字符串
char** DealCommand(char* command)
{
  if(command==NULL&&*command=='\0')
  {
    printf("dealcommand is error\n");
    return NULL;
  }

  //用来保存命令
  static char* argv[1024]={0};
  int argc=0;
  while(*command)
  {
    //isspace函数用来去掉多余的空格
    //isspace的返回值:如果当前字符是空格返回1,否则返回0
    //注意'\0'在issapce函数中不算空格的,所以要进行判断
    while(!isspace(*command)&&*command!='\0')
    {
      argv[argc]=command;
      argc++;

      //去找下一个空格
      while(!isspace(*command)&&*command!='\0')
      {
        command++;
      }
      *command='\0';
    }
    command++;
  }
  argv[argc]=NULL;

  //for(int i=0;i<argc;i++)
  //{
  //  printf("%d:%s   ",i,argv[i]);
  //}
  //printf("\n");

  return argv; 
}

//进行程序替换
int exec()
{
  char** argv=DealCommand(g_command);
  pid_t pid =fork();
  if(pid<0)
  {
    printf("foek is error!\n");
    return -1;
  }
  else if(pid==0)
  {
    //child
    //如果argc中为NULL,就直接返回
    if(argv[0]==NULL)
    {
      exit(-1);
    }
    //进行替换,execvp第一个参数是可执行程序名,第二个参数是该可执行程序的参数组成的数组
    execvp(argv[0],argv);
    //execl("/usr/bin/ls","ls","-a",NULL);
  }
  else
  {
    //father
    waitpid(pid,NULL,0);
  }
  return 0;
}

int main()
{
  //循环读数据
  while(1)
  {
    printf("[dev@localhost dev]$ ");
    int ret = GetCommand();
    if(ret == -1)
    {
      //如果读取失败,继续循环读取,不能直接break调
      continue;
    }

    //处理解析数据
    //char** argv = DealCommand(g_command);

    //进行替换
    //exec(argv);
    exec();
  }

  return 0;
}

发布了49 篇原创文章 · 获赞 15 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/wolfGuiDao/article/details/103828503