进程控制——进程的程序替换

在这里插入图片描述

欢迎来到Cefler的博客
博客主页:那个传说中的man的主页
个人专栏:题目解析
推荐文章:题目大解析(3)


目录

  • 进程的程序替换概念
  • 六个替换函数
  • 单进程版的程序替换的代码(没有子进程)--见见程序替换
  • 程序替换我们自己写的程序
  • 程序替换Shell脚本
    • shell脚本概念
    • 程序替换
  • 程序替换和环境变量
    • 程序替换时,子进程的环境变量哪来的?
    • 传递自己构造的环境变量表
  • 简易的shel

进程的程序替换概念

之前我们所创建的所有的子进程,执行的代码,都是父进程代码的一部分,如果我们想让子进程执行新的程序,不再和父进程有瓜葛了。这时我们就引入—— 程序替换 程序替换 程序替换
概念
在Linux中,进程控制的程序替换是指一个进程用另一个新程序来替换自己的执行映像。这个新程序完全取代了原来的程序,包括代码、数据和堆栈等信息,成为了新的运行中的进程。这种程序替换的操作通常是通过调用exec函数来完成的。

程序替换常用于创建新的进程或者更新正在运行的进程的执行映像。例如,在一个Shell中,当你输入一个命令时,Shell就会创建一个新的进程来执行相应的程序。在这种情况下,Shell就会调用fork创建一个新的子进程,然后在子进程中调用exec来加载并执行新的程序。

程序替换的一个重要特点是原有的进程ID(PID)不会改变,这意味着虽然进程的执行映像已经被替换,但是它仍然保持着原来的PID。这样做的好处是可以保持一些与PID相关的状态,比如父子进程关系、进程的权限等。

总之,Linux中进程控制的程序替换是通过exec函数来实现的,它允许一个进程用另一个程序来替换自己的执行映像,从而创建新的进程或者更新正在运行的进程的执行映像。

六个替换函数

在Linux中,有多个替换函数可用于执行其他程序。下面是对这些函数的介绍:

  1. execl:该函数用于在当前进程中执行一个可执行文件。它接受可执行文件的路径和一系列的参数作为参数,并将当前进程替换为新的可执行文件。这个函数的原型如下:
    #include 
    int execl(const char *path, const char *arg, ...);
    
    这个函数在执行成功后不会返回,如果调用失败,则会设置errno并返回-1[1]。

进程控制——进程的程序替换_第1张图片

  1. execlp:与execl类似,但是它可以在系统的PATH环境变量指定的路径中搜索可执行文件。这个函数的原型如下:

    #include 
    int execlp(const char *file, const char *arg, ...);
    

    同样地,这个函数在执行成功后不会返回,如果调用失败,则会设置errno并返回-1[1]。
    进程控制——进程的程序替换_第2张图片

  2. execle:这个函数与execl类似,但是它还接受一个额外的参数envp,用于指定新程序的环境变量。这个函数的原型如下:

    #include 
    int execle(const char *path, const char *arg, ..., char * const envp[]);
    

    同样地,这个函数在执行成功后不会返回,如果调用失败,则会设置errno并返回-1[1]。

  3. execv:与execl类似,但是它接受一个参数数组来传递可执行文件的路径和参数。这个函数的原型如下:

    #include 
    int execv(const char *path, char *const argv[]);
    

    同样地,这个函数在执行成功后不会返回,如果调用失败,则会设置errno并返回-1[1]。

进程控制——进程的程序替换_第3张图片

  1. execvp:与execv类似,但是它可以在系统的PATH环境变量指定的路径中搜索可执行文件。这个函数的原型如下:
    #include 
    int execvp(const char *file, char *const argv[]);
    
    同样地,这个函数在执行成功后不会返回,如果调用失败,则会设置errno并返回-1[1]。

进程控制——进程的程序替换_第4张图片

  1. execve:与execv类似,但是它还接受一个额外的参数envp,用于指定新程序的环境变量。这个函数的原型如下:
    #include 
    int execve(const char *path, char *const argv[], char *const envp[]);
    
    同样地,这个函数在执行成功后不会返回,如果调用失败,则会设置errno并返回-1[1]。
    这个函数才是其它替换函数调用的底层函数
    进程控制——进程的程序替换_第5张图片

进程控制——进程的程序替换_第6张图片

这些以exec开头的函数,统称exec函数

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

这些替换函数可以在创建新进程后,用于在新进程中执行另一个程序。当调用这些函数时,当前进程会被完全替换为新程序。这些函数的区别在于参数的传递方式和对环境变量的处理方式。通过这些函数,我们可以方便地在Linux中执行其他程序并传递参数[1]。

exe函数的本质就是加载器,将磁盘中的程序加载到内存中!

单进程版的程序替换的代码(没有子进程)–见见程序替换

代码:

#include
#include
int main()
{
	printf("pid:%d,exe command begin\n", getpid());
	execl("/usr/bin/pwd", "pwd", NULL);
	printf("pid:%d,exe command end\n", getpid());

	return 0;
}

result:
在这里插入图片描述
原理解释:
进程控制——进程的程序替换_第7张图片
进程控制——进程的程序替换_第8张图片

程序替换我们自己写的程序

首先在Makefile文件中使得make能一次性创建两个可执行程序
进程控制——进程的程序替换_第9张图片
创建mytest.cc:
进程控制——进程的程序替换_第10张图片
myprocess.c:
进程控制——进程的程序替换_第11张图片
the result:
进程控制——进程的程序替换_第12张图片

程序替换Shell脚本

shell脚本概念

当谈论Linux的Shell脚本文件时,我们实际上指的是一种文本文件,其中包含了一系列的Shell命令,这些命令按照特定的顺序被解释器(比如bash、sh等)逐行执行,就像在命令行中一行行输入命令一样。

下面是一个简单的Shell脚本例子,它将会创建一个包含当前日期和时间的文件,并显示一条消息:

#!/usr/bin/bash
# 这是一个简单的Shell脚本

# 显示一条消息
echo "hello shell"

让我们来解释一下这个例子:

  • #!/usr/bin/bash:这是脚本文件的第一行,称为shebang,用来告诉系统使用哪个解释器来执行脚本。在这个例子中,#!/usr/bin/bash告诉系统使用Bash来解释执行脚本。

  • #开头的行是注释,用来对脚本进行说明,不会被解释器执行。

  • echo "hello shell":这一行使用echo命令在终端上显示一条消息。

要注意的是,为了使一个Shell脚本文件可以被执行,你需要先将其设置为可执行文件。你可以使用chmod +x script.sh命令来赋予这个Shell脚本文件执行权限。

总的来说,Linux的Shell脚本文件是一种包含了一系列Shell命令的文本文件,它可以用来自动化执行一系列任务,非常适合用于批处理、系统管理和自动化部署等场景。

程序替换

script.sh:

在这里插入图片描述
myprocess.c
进程控制——进程的程序替换_第13张图片
the result:
进程控制——进程的程序替换_第14张图片

程序替换和环境变量

程序替换时,子进程的环境变量哪来的?

当我们进行程序替换时,子进程对应的环境变量,是可以直接从父进程来的(这就可以解释环境变量的全局性)

而子进程的环境变量,并不是因为程序替换来的,而是在进程继承中,继承进程地址空间里面的数据(数据中包含父进程的环境变量),而程序替换:只会替换新程序的代码和数据,环境变量不会被替换!

我们在进程状态、类型、优先级、命令行参数概念、环境变量(重要)、程序地址空间中知道main函数有argc,argv,env三个关于命令行的参数。实际上我们上面学的替换函数,就是将命令打包成char* argv[],environ打包成char* env[]在程序替换中进行传递
进程控制——进程的程序替换_第15张图片
关于上图中的environ参数,我们可以直接传,也可以不显示传,它都会默认传递的

传递自己构造的环境变量表

进程控制——进程的程序替换_第16张图片
在这里插入图片描述
execle程序替换传递环境变量时,是原地覆盖,而不是新增。
若要新增的话,可以用到函数putenv()
进程控制——进程的程序替换_第17张图片

简易的shel

#include
#include
#include
#include
#include
#include

#define NUM 1024
#define SIZE 64
#define SEP " "

char cwd[1024];
char enval[1024];
int lastcode = 0;
//#define Debug 1
const char* getUsername()
{
  const char*name = getenv("USER");
  if(name) return name;
  else return "none";
}
const char* getHostname()
{
  const char* hostname = getenv("HOSTNAME");
  if(hostname) return hostname;
  else return "none";
}
const char* getCwd()
{
  const char* cwd = getenv("PWD");
  if(cwd) return cwd;
  else return "none";
}
int getUserCommand(char* usercommand,int num)
{

  printf("[%s@%s %s]# ",getUsername(),getHostname(),getCwd());
 char* r =  fgets(usercommand,num,stdin);
  if(r==NULL) return 1;
  usercommand[strlen(usercommand)-1] = '\0';//消除最后一个输入的\n
  return strlen(usercommand);
}
void commandSplit(char* in,char* out[])
{
  int argc = 0;
  out[argc++] = strtok(in,SEP);//第一次分隔需要传入源字符串
  while(out[argc++] = strtok(NULL,SEP));//当strtok传回NULL时就结束
#ifdef Debug
  for(int i = 0;out[i];i++)
  {
    printf("%d:%s\n",i,out[i]);
  }
#endif
}
int execute(char* argv[])
{
  pid_t id = fork();
  if(id<0) return -1;
  else if(id==0)//child
  {
    //exec command
    execvp(argv[0],argv);//argv[0]第一个就是执行命令
    exit(1);
  }
  else
  {
    //father
    int status = 0;
    pid_t rid = waitpid(id,&status,0);
    if(rid>0)
    {
      lastcode = WEXITSTATUS(status);//
    }

  }
  return 0;
}
void cd(const char* path)
{
  chdir(path);
  char tmp[1024];//创建临时变量存储
  getcwd(tmp,sizeof(tmp));//getcwd获取当前路径
  sprintf(cwd,"PWD=%s",tmp);//tmp读入到cwd中
  putenv(cwd);//将新的PWD环境变量导出
}
int doBuildin(char* argv[])
{
  if(strcmp(argv[0],"cd")==0)
  {
    char* path = NULL;
    if(argv[1] == NULL) path = ".";
    else path = argv[1];

    cd(path);
    return 1;
  }
   else if(strcmp(argv[0], "export") == 0)
    {
        if(argv[1] == NULL) return 1;
        strcpy(enval, argv[1]);
        putenv(enval); // ???
        return 1;
    }
    else if(strcmp(argv[0], "echo") == 0)
    {
        char *val = argv[1]+1; // $PATH $?
        if(strcmp(val, "?") == 0)
        {
            printf("%d\n", lastcode);
            lastcode = 0;
        }
        else{
            printf("%s\n", getenv(val));
        }
        return 1;
    }
    else if(0){}
  return 0;
}

int main()
{
  while(1){
  char usercommand[NUM];
  //1.打印提示符&&获取用户命令符字符串获取成功
 int n =  getUserCommand(usercommand,sizeof(usercommand));
 if(n<=0) continue;//如果长度小于等于0就没必要执行相关命令了
  char* argv[SIZE];
  //2.分割字符串
  commandSplit(usercommand,argv);
  //3.检查是否为内建命令,内建命令不能让子进程进行,让父进程自己执行
  n = doBuildin(argv);
  if(n) continue;
  //4.执行相对应的命令
  execute(argv);
  }
  return 0;
}


如上便是本期的所有内容了,如果喜欢并觉得有帮助的话,希望可以博个点赞+收藏+关注❤️ ,学海无涯苦作舟,愿与君一起共勉成长
进程控制——进程的程序替换_第18张图片
在这里插入图片描述

你可能感兴趣的:(Linux,linux,进程的程序替换)