【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!!!

相关推荐
孙克旭_23 分钟前
PXE_Kickstart_无人值守自动化安装系统
linux·运维·自动化
皓月盈江1 小时前
Linux电脑本机使用小皮面板集成环境开发调试WEB项目
linux·php·web开发·phpstudy·小皮面板·集成环境·www.xp.cn
深井冰水1 小时前
mac M2能安装的虚拟机和linux系统系统
linux·macos
leoufung2 小时前
内核内存锁定机制与用户空间内存锁定的交互分析
linux·kernel
橙子199110162 小时前
在 Kotlin 中什么是委托属性,简要说说其使用场景和原理
android·开发语言·kotlin
androidwork2 小时前
Kotlin Android LeakCanary内存泄漏检测实战
android·开发语言·kotlin
菜菜why3 小时前
AutoDL租用服务器教程
服务器
IT专业服务商3 小时前
联想 SR550 服务器,配置 RAID 5教程!
运维·服务器·windows·microsoft·硬件架构
笨鸭先游3 小时前
Android Studio的jks文件
android·ide·android studio
gys98953 小时前
android studio开发aar插件,并用uniapp开发APP使用这个aar
android·uni-app·android studio