Linux知识点 -- 进程控制(二)

Linux知识点 – 进程控制(二)

文章目录

  • Linux知识点 -- 进程控制(二)
  • 一、进程程序替换
    • 1.概念
    • 2.替换原理
    • 3.进程替换的操作
    • 4.使用exec函数执行自己写的程序
    • 5.使用exec函数执行其他语言的程序
  • 二、编写一个简易的shell


一、进程程序替换

1.概念

fork之后,父子进程各自执行父进程代码的一部分,父子代码共享,数据写时拷贝各自一份,如果子进程想执行一个全新的程序,就需要使用程序替换;

  • 程序替换,是通过特定的接口,将磁盘上的一个权限的程序(代码和数据),加载到调用进程的地址空间中;

2.替换原理

  • 用fork创建子进程后执行的是和父进程相同的程序(但有可能是不同的代码分支),子进程往往要调用一种exec函数以执行另一个程序。当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。调用exec并不创建新的进程,所以调用exec前后该进程的id并未改变;

Linux知识点 -- 进程控制(二)_第1张图片

3.进程替换的操作

exec函数:
Linux知识点 -- 进程控制(二)_第2张图片
上图这些函数统称exec函数;
(1)execl函数:

  • path:找到程序,路径 + 目标文件名;
  • arg,…:可变参数列表,可以传入多个不定个数的参数;
    我们在命令行上怎么执行,这里的参数就怎么填;
    最后一个参数必须是NULL,标识参数传递完毕;
  • 不创建子进程:
    Linux知识点 -- 进程控制(二)_第3张图片
    运行结果:
    Linux知识点 -- 进程控制(二)_第4张图片
    从结果中可以看出,execl函数执行了我们传给它的"ls -l"指令,但是execl函数之后的代码没有被执行;
    因为execl函数是程序替换,调用该函数成功之后,会将当前进程的所有的代码和数据都进行替换,包括已经执行的和没有执行的;所以,一旦调用成功后,execl后面的所有代码都不会执行;
    execl调用成功后,没有返回值,一旦execl有返回值,一定是调用失败了,所以execl不需要进行函数返回值判定;

    若替换不存在的代码:
    Linux知识点 -- 进程控制(二)_第5张图片
    运行结果:
    在这里插入图片描述
    execl是不会进行程序替换的
    Linux知识点 -- 进程控制(二)_第6张图片
    所以应该直接在execl函数后跟exit(1),若execl替换失败,一定会执行后面的代码,直接结束进程,查询进程退出码为1,就是调用失败
    Linux知识点 -- 进程控制(二)_第7张图片
  • 创建子进程:
    父进程阻塞式等待,子进程来进行进程替换,将之后的执行代码替换为 ls -a -l;
    Linux知识点 -- 进程控制(二)_第8张图片
    运行结果:
    Linux知识点 -- 进程控制(二)_第9张图片
  • 为什么要创建子进程?
    为了不影响父进程,我们想让父进程聚焦在读取数据,解析数据,指派进程执行代码的功能,如果不创建子进程,那么我们替换的进程只能是父进程,如果创建了,替换的就是子进程,而不影响父进程;
    子进程在加载新程序的时候,父子进程的代码和数据就已经分离了,而不是像原来一样,代码共用,部分数据写时拷贝;

(2)execv函数:
在这里插入图片描述

  • path:找到程序,路径 + 目标文件名;
  • argv:参数列表,是一个存放命令的参数的指针数组,最后的参数必须为NULL;
    Linux知识点 -- 进程控制(二)_第10张图片
    execv与execl功能是一样的,只是传参的方式不一样;

Linux知识点 -- 进程控制(二)_第11张图片
运行结果:
Linux知识点 -- 进程控制(二)_第12张图片
(3)execlp函数:
在这里插入图片描述

  • file:指向文件名的指针;execlp只需给出文件名就可以了,因为函数会自己在环境变量PATH中查找
  • arg,…:可变参数列表,可以传入多个不定个数的参数;

Linux知识点 -- 进程控制(二)_第13张图片
(4)execvp函数:
在这里插入图片描述

  • file:指向文件名的指针;execlp只需给出文件名就可以了,因为函数会自己在环境变量PATH中查找
  • argv:参数列表,是一个存放命令的参数的指针数组,最后的参数必须为NULL;

Linux知识点 -- 进程控制(二)_第14张图片

(5)execle函数:
在这里插入图片描述

  • path:找到程序,路径 + 目标文件名;
  • arg,…:可变参数列表,可以传入多个不定个数的参数;
  • envp:环境变量,可以给替换的程序传入当前进程的环境变量;

test.cc是获取环境变量的程序,获取MY_VAL的值:
Linux知识点 -- 进程控制(二)_第15张图片
单独执行时,获取不到环境变量MY_VAL的值:
在这里插入图片描述
在replace.cc的中给环境变量MY_VAL赋值,子进程中使用execle函数进行进程替换,执行test.cc函数:
Linux知识点 -- 进程控制(二)_第16张图片
运行结果:
Linux知识点 -- 进程控制(二)_第17张图片
因为环境变量具有全局属性,可以被子进程继承下来,也可以直接传父进程的main函数的env参数:
Linux知识点 -- 进程控制(二)_第18张图片
(6)execve函数:
Linux知识点 -- 进程控制(二)_第19张图片
*execve是单独在一个手册里的,包含在unistd.h头文件中,这是一个系统调用接口,其他的exec函数都是execve的封装后的接口,用来满足不同场景的调用需求;
Linux知识点 -- 进程控制(二)_第20张图片

  • filename:指向文件名的指针;execlp只需给出文件名就可以了,因为函数会自己在环境变量PATH中查找
  • argv:参数列表,是一个存放命令的参数的指针数组,最后的参数必须为NULL;
  • envp:环境变量,可以给替换的程序传入当前进程的环境变量;

总结:

  • l代表第二个参数为可变参数列表;
  • v代表第二个参数为参数指针数组;
  • p代表可以在环境变量中查找文件,第一个参数可以传文件名;没有p代表第一个参数需要传文件路径;
  • e代表自己维护环境变量

4.使用exec函数执行自己写的程序

  • 先写一个读取命令行参数的程序mycmd.cc:
    Linux知识点 -- 进程控制(二)_第21张图片
  • 使用execlp函数在子进程中运行自己的mycmd:
    Linux知识点 -- 进程控制(二)_第22张图片
    文件路径采用绝对路径
  • 编写一次能生成两个可执行程序的makefile
    Linux知识点 -- 进程控制(二)_第23张图片
  • 运行结果:
    在这里插入图片描述
  • 若execlp的文件路径采用相对路径,运行结果为:
    在这里插入图片描述
    在这里插入图片描述
    运行成功;

5.使用exec函数执行其他语言的程序

  • python
    Linux知识点 -- 进程控制(二)_第24张图片
    Linux知识点 -- 进程控制(二)_第25张图片
    运行结果:
    Linux知识点 -- 进程控制(二)_第26张图片
  • bash
    Linux知识点 -- 进程控制(二)_第27张图片
    Linux知识点 -- 进程控制(二)_第28张图片
    运行结果:Linux知识点 -- 进程控制(二)_第29张图片

二、编写一个简易的shell

Linux知识点 -- 进程控制(二)_第30张图片

  #include 
  #include 
  #include 
  #include 
  #include 
  #include 
  
  #define NUM 1024
  #define SIZE 32
  #define SEP " "
  
  //保存完整的命令行字符串
  char cmd_line[NUM];
  //保存打散之后的命令行字符串
  char* g_argv[SIZE];
  
  //shell运行原理,让子进程执行命令,父进程等待&&解析命令
  int main()
  {
      //命令行解释器:一定是一个常驻内存的进程,不退出
      while(1)
      {
          //1.打印出提示信息 [lmx@localhost myshell]#
          printf("[lmx@localhost myshell]# ");
          fflush(stdout);//由于printf没有加\n,不刷新缓冲区,使用fflush刷新
          memset(cmd_line, '\0', sizeof cmd_line);//sizeof可以不使用括号
  
          //2.获取用户的键盘输入,输入的是各种指令和选项:"ls -a -l"
          if(fgets(cmd_line, sizeof cmd_line, stdin) == NULL)
          {
              continue;
          }
          cmd_line[strlen(cmd_line) - 1] = '\0';//去掉输入时的\n
          //"ls -a -l\n\0"
                                                                                
          //3.命令行字符串解析:"ls -a -l" -> "ls" "-a" "-l"
          g_argv[0] = strtok(cmd_line, SEP);//第一次调用,要传入原始字符串
          int index = 1;
          if(strcmp(g_argv[0], "ls") == 0)
          {
              g_argv[index++] = "--color=auto";
          }
          if(strcmp(g_argv[0], "ll") == 0)
          {
              g_argv[0] = "ls";                                                 
              g_argv[index++] = "-l";
              g_argv[index++] = "--color=auto";
          }
  
          while(g_argv[index++] = strtok(NULL, SEP));//第二次,如果还要继续解析>  原始字符,传入NULL
  
          //4.TODO:内置命令,让父进程(shell)自己执行的命令,叫做内置命令,内建  命令
          //内置命令本质是shell中的一个函数调用
          if(strcmp(g_argv[0], "cd") == 0)//如果命令是cd,改变工作目录,需要在父  进程实现
                                          //子进程的cd变换的只是子进程的路径,父  进程不会变
          {
              if(g_argv[1] != NULL)
              {
                  chdir(g_argv[1]);//改变工作目录
              }
              continue;
          }
  
          //5.fork()
          pid_t id = fork();
          if(id == 0)//child
          {
              execvp(g_argv[0], g_argv);
              exit(1);
          }
  
          //father
          int status = 0;
          pid_t ret = waitpid(id, &status, 0);//阻塞等待
          if(ret > 0)
          {
              printf("exit code: %d\n", WEXITSTATUS(status));
          }
      }
      
      return 0;
  }

效果:
Linux知识点 -- 进程控制(二)_第31张图片

你可能感兴趣的:(Linux,linux,服务器,运维)