【Linux】进程替换

进程替换

一、概念

当我们fork()生成子进程后,子进程的代码与数据可以来自其他可执行程序。把磁盘上其他程序的数据以覆盖的形式给子进程。这样子进程就可以执行全新的程序了,这种现象称为程序替换。

二、原理

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

三、替换函数

1. execl

①函数原型:

c 复制代码
int execl(const char*path,const char*arg,...)

②参数解析:

  • path:为可执行程序的路径;
  • arg:如何执行这个可执行程序(执行该程序的指令);
  • ... :指的是给这执行程序携带的参数,在参数末尾加NULL表示参数结束;
  • 返回值:函数调用失败返回-1,成功不返回

③示例:

替换成功:

c 复制代码
#include<stdio.h>
#include<unistd.h>
int main()
{
    printf("before\n");
    execl("/usr/bin/ls","ls","-a","-l",NULL);
    printf("after\n");
    return 0;
}

替换失败:

c 复制代码
#include<stdio.h>
#include<unistd.h>
int main()
{
    printf("before\n");
    execl("/usr/bin/la","ls","-a","-l",NULL);
    printf("after\n");
    return 0;
}

通过对比可以发现,替换成功以后,原程序后面的代码就不执行了,执行另一个程序 的代码,失败就接着执行原程序的代码。

2. execlp

①函数原型:

c 复制代码
 int execlp(const char *file, const char *arg, ...);

②参数解析:

  • file:要替换的目标程序;
  • arg:如何执行这个程序,...为给这个程序传的参数;
  • 返回值:函数调用失败返回-1;

③示例:

比较于execl,execp默认在Linux环境变量PATH中查找可执行程序,所以传参可直接传可执行程序的名字,不用传绝对路径。

c 复制代码
#include<stdio.h>
#include<unistd.h>
int main()
{
    printf("before\n");
    execlp("ls","ls","-a","-l",NULL);
    printf("after\n");
    return 0;
}

3.execle

①函数原型:

c 复制代码
int execle(const char *path, const char *arg, ..., char * const envp[]);

②参数解析:

  • path:替换目标程序路径;
  • arg:如何执行这个程序,...为给这个程序传的参数;
  • envp数组:要导入的环境变量;
  • 返回值:失败返回-1;

③示例:

查看环境变量代码,用来验证后面的结果

c 复制代码
#include<stdio.h>
#include<stdlib.h>
int main()
{
    printf("path:%s\n",getenv("PATH"));
    printf("aaa:%s\n",getenv("aaa"));
    return 0;
}
c 复制代码
#include<stdio.h>
#include<unistd.h>
int main()
{
    char* envp[]={"aaa=hello",NULL};
    printf("before\n");
    execle("./a.out","./a.out",NULL,envp);
    printf("after\n");
    return 0;
}

注意:

  1. 导环境变量的数组最后以NULL结尾
  2. 导入环境变量后原系统环境变量的值被清空,这种导入环境变量的方式为覆盖式导入

4.execv

①函数原型:

c 复制代码
int execv(const char *path, char *const argv[]);

②参数解析:

  • path:替换目标程序路径;
  • argv数组:保存的是参数列表,将如何执行 可执行程序 和可执行程序需要的参数保存到字符串数组中,最后以NULL结尾表示参数结束;
  • 返回值:进程替换失败返回-1

③示例:

c 复制代码
#include<stdio.h>
#include<unistd.h>
int main()
{
    char* argv[]={"ls","-a","-l",NULL};
    printf("before\n");
    execv("/usr/bin/ls",argv);
    printf("after\n");
    return 0;
}

注:可以发现argv数组与main函数的命令行参数相同

5.execvp

①函数原型:

c 复制代码
int execvp(const char *file, char *const argv[]);

②参数解析:

  • file:要替换的目标程序;
  • argv数组:保存的是参数列表,将如何执行 可执行程序 和可执行程序需要的参数保存到字符串数组中,最后以NULL结尾表示参数结束;

③示例:略

6.execvpe

①函数原型:

c 复制代码
 int execvpe(const char *file, char *const argv[], char *const envp[]);

②参数解析:

  • file:要替换的目标程序;
  • argv数组:保存的是参数列表,将如何执行 可执行程序 和可执行程序需要的参数保存到字符串数组中,最后以NULL结尾表示参数结束;
  • envp数组:要导入的环境变量;

③示例:略

小结:

替换函数前面的exec不变

  • l:参数采用列表
  • v:参数采用数组
  • p:不需要输入路径,在环境变量自动搜索
  • e:要导入自己的环境变量

所以execvp表示不需要输入路径,参数用数组传

execve表示需要输入路径,参数用数组传,自己维护环境变量

四、实现一个简易的shell

c 复制代码
#include<stdio.h>
#include<unistd.h>
#include<assert.h>
#include<stdlib.h>
#include<sys/wait.h>
#include<sys/types.h>
#include<string.h>
#define MAX 1024
#define ARGC 64
#define SEP " "

//将输入的字符串切割并保存到argv中
int split(char*commandstr,char*argv[])
{
      assert(commandstr);
      assert(argv);
      argv[0]=strtok(commandstr,SEP);
      if(argv[0]==NULL)return -1; //若为NULL,则重新输入
      int i=1;
      while(argv[i++]=strtok(NULL,SEP));
      return 0;
}
int main()
{ while(1)
 {
      char commandstr[MAX]={NULL}; //用于保存用户输入的指令
      char*argv[ARGC]={NULL};
    printf("[lx@hecs-%d myshell]$ ",getpid());
    fflush(stdout);
    char*s=fgets(commandstr,sizeof(commandstr),stdin); //获取指令
    assert(s);
    (void)s;
    commandstr[strlen(commandstr)-1]='\0'; //去掉键盘输入的'\n'
    int n=split(commandstr,argv);  //切割输入的指令字符串
    if(n!=0)continue;
    pid_t id=fork();
     if(id==0)
     {
        execvp(argv[0],argv); //程序替换
        exit(0);
     }
     int status=0;
     waitpid(id,&status,0); //等待子进程
 }
    return 0;
}

可以看到简易版的myshell就实现好了。

myshellplus

c 复制代码
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <assert.h>

#define MAX 1024
#define ARGC 64
#define SEP " "

int split(char* commandstr,char* argv[])
{
  assert(commandstr);
  assert(argv);

  argv[0] = strtok(commandstr,SEP);
  if(argv[0] == NULL) return -1;
  int i = 1;
  while((argv[i++] = strtok(NULL,SEP)));
  return 0;
}

void showEnv()
{
  extern char** environ;
  for(int i = 0; environ[i]; i++) printf("%d:%s\n",i,environ[i]);
}

int main()
{
  extern int putenv(char* string);
  char myenv[32][256];
  int env_index = 0;
  int exitCode = 0;

  while(1)
  {
    char commandstr[MAX] = {0};
    char* argv[ARGC] = {NULL};
    
    printf("[hxy@mychaimachine]$ ");
    fflush(stdout);
    char* s = fgets(commandstr,sizeof(commandstr),stdin);
    assert(s);
    (void)s;

    commandstr[strlen(commandstr) - 1] = '\0'; // 去掉键盘输入的\n

    int n = split(commandstr,argv); // 切割字符串
    if(n != 0) continue;
    
    if(strcmp(argv[0],"cd") == 0)
    {
      if(argv[1] != NULL) chdir(argv[1]);
      continue;
    }
    else if(strcmp(argv[0],"export") == 0)
    {
      if(argv[1] != NULL)
      {
        strcpy(myenv[env_index],argv[1]); // 用户自己定义的环境变量,需要bash自己来维护
        putenv(myenv[env_index++]);
      }
      continue;
    }
    else if(strcmp(argv[0],"env") == 0)
    {
      showEnv(); // env查看环境变量时,其实看的是父进程bash的变量
      continue;
    }
    else if(strcmp(argv[0],"echo") == 0)
    {
      const char* target_env = NULL;
      if(argv[1][0] == '$')
      {
        if(argv[1][1] == '?')
        {
          printf("%d\n",exitCode);
          continue;
        } 
        else target_env = getenv(argv[1] + 1);

        if(target_env != NULL) printf("%s = %s\n",argv[1] + 1,target_env);
      }
      continue;
    }

    // ls设置颜色选项
    if(strcmp(argv[0],"ls") == 0)
    {
      int pos = 0;
      while(argv[pos] != NULL)
      {
        pos++;
      }
      argv[pos++] = (char*)"--color=auto";
      argv[pos] = NULL;
    }
        

    pid_t id = fork();
    if(id == 0)
    {
      // 子进程
      execvp(argv[0],argv);
      exit(1);
    }

    int status = 0;
    pid_t ret = waitpid(id,&status,0);
    if(ret > 0)
    {
      exitCode = WEXITSTATUS(status); // 获取最近一次进程的退出码
    }
  }
  return 0;
}

进程替换的知识就讲到这了,如有错误还望指出,886!!!

相关推荐
Y多了个想法11 分钟前
RK3568 android11 适配敦泰触摸屏 FocalTech-ft5526
android·rk3568·触摸屏·tp·敦泰·focaltech·ft5526
嚯——哈哈17 分钟前
AWS云服务器:开启高效计算的新纪元
服务器·云计算·aws
嚯——哈哈17 分钟前
从入门到精通:解析如何使用亚马逊云服务器(AWS EC2)
运维·服务器·aws
编程修仙1 小时前
Collections工具类
linux·windows·python
芝麻团坚果1 小时前
对subprocess启动的子进程使用VSCode python debugger
linux·ide·python·subprocess·vscode debugger
写点什么啦1 小时前
[debug]不同的window连接ubuntu的vscode后无法正常加载kernel
linux·vscode·ubuntu·debug
NotesChapter1 小时前
Android吸顶效果,并有着ViewPager左右切换
android
wellnw1 小时前
[ubuntu]编译共享内存读取出现read.c:(.text+0x1a): undefined reference to `shm_open‘问题解决方案
linux·ubuntu
不爱学习的YY酱1 小时前
【操作系统不挂科】<CPU调度(13)>选择题(带答案与解析)
java·linux·前端·算法·操作系统
DC_BLOG1 小时前
Linux-Nginx虚拟主机
linux·运维·nginx