【Linux】进程程序替换

为什么要使用进程程序替换?

一般情况下,我们写程序的时候,只能调用同一语言的接口。但是如果我们使用的C++语言,但是想要调用python或者java的程序接口,该怎么办?这时,进程程序替换就起到了作用。

什么是进程程序替换

进程程序替换:将可执行程序加载到内存,并且重新调整子进程的页表映射,使之指向新的进程的代码和数据段,这种过程就叫做程序替换。

一般情况下,会使用fork()函数创建子进程,然后让子进程进行进程程序替换,父进程继续执行父进程的逻辑。

进程程序替换的原理

观察现象

在这里插入图片描述
我们写了这样一个程序,这里的execl()函数就是进程程序替换函数,先就这样用着,后文会详细讲解这类函数。

本程序很简单,就是最开始打印了一下"before,进程的pid,ppid",再进行程序替换,最后再打印一下"after,进程的pid,ppid"。

运行之后,观察结果:
在这里插入图片描述
运行之后,发现两个很奇怪的情况:

  1. 只打印了before,没有打印after
  2. 打印之后执行了ls命令,可是我们知道,我们并没有输入任何指令

从这个简单的例子中,我们认识了进程程序替换。

可以观察到,我们只用的execl()函数中,里面有ls命令,所以是execl函数直接进行了程序替换。导致程序运行到execl之后,跑去执行ls命令了,不再执行本来的命令。

同样的情况,我们可以将execl()函数中的ls命令改为top命令,观察是不是直接执行了top函数。

在这里插入图片描述
根据上图所示可以看到确实执行了top执行,所以确实是我们所说那样。

原理

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

程序替换的原理

  1. 将磁盘中的程序,加载入内存结构。
  2. 重新建立页表映射,如果谁执行程序替换,就重新建立子进程的页表映射。

父进程的映射关系
在这里插入图片描述
程序替换之后,父子进程的映射关系

在这里插入图片描述
在这里插入图片描述

调整子进程的页表,让其不再与父进程代码和数据有任何关系,而是指向自己的代码和自己的数据区。

注意:这个过程中没有创建新的进程!

总结:

  1. 让fork创建子进程,不让子进程执行父进程的程序,而是执行磁盘中新的程序
  2. 执行磁盘中新的程序,但是不创建新的进程
  3. 子进程的内核数据结构没有改变,改变的是,页表右边的部分 — 也就是虚拟内存和物理内存的映射关系

进程程序替换的接口 – exec*家族

输入man execl查看手册
在这里插入图片描述
可以发现所有函数都有相同的开头,就是exec,所以后面的后缀才能区分他们。

提前提出总结:

  1. exec + l这样的后面加上l的函数,这里的l表示list(列表),就表示参数中会有…,如果不使用就要在…位置传上NULL (…是可变参数列表)
  2. exec + v这样的后面加上v的函数,这里的v表示vector(数组),就表示参数的第二个函数是argv[],要传递命令行参数
  3. exec + p这样的后面加上p的函数,这里的p表示path,就表示函数的第一个参数不用像execl这样没p的函数一样传path(路径名),传递file(文件名)就行
  4. exec + e这样的后面加上e的函数,这里的e表示environment,就表示函数的最后一个参数需要传递环境变量

execl()

int execl(const char *path, const char *arg,);
  • 这是exec + l,说明是list,并且最后一个参数是可变参数。

在这里插入图片描述
上面图片大概解释了函数的使用逻辑。

path

  • 可执行程序的路径。
    比如:如果是执行“ls”命令,该命令在"/usr/bin/ls"路径下,所以在该参数位置,填下这个路径。

arg

  • 命令怎么写,这里的命令行参数就如何填写
  • 解决如何执行程序的问题
    例如:你写的命令是"ls -a -l",那么在命令行参数填写*“ls”, “-a”,“-l”*就行

  • 可变参数,可以传多个参数
    如果你不使用,就填上NULL就行

** 代码演示**
在这里插入图片描述
运行结果:
在这里插入图片描述
程序替换没有返回值
----程序替换是没有返回值的,如果有返回值就代表程序替换失败了!

execv()

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

execv函数是exec + v,说明是vector,那么第二个参数就是argv[],并且没有p,说明第一个参数需要传path。

参数:

path

  • 可执行程序的路径。
    比如:如果是执行“ls”命令,该命令在"/usr/bin/ls"路径下,所以在该参数位置,填下这个路径。

argv[]

  • 和execl一样,但是传参的方式略有区别
  • 这里需要注意的是,最后一个参数必须是NULL
    在这里插入图片描述
  • 如果想不报警告,将参数强转成(char*)类型就行
    在这里插入图片描述
    代码演示
  1 #include<stdio.h>
  2 #include<unistd.h>
  3 #include<stdlib.h>
  4 #include<sys/types.h>
  5 #include<sys/wait.h>
  6 
  7 int main()
  8 {
   
  9 
 10   printf("开始前:\n");
 11 
 12   pid_t id = fork();
 13   if(id == 0)
 14   {
   
 15     //child
 16   char *const myargv[] = {
   
 17     (char*)"ls",
 18     (char*)"-l",
 19     (char*)"-a",
 20     NULL
 21   };
 22    execv("/usr/bin/ls", myargv);                                                                                                           
 23   }
 24   else
 25   {
   
 26     pid_t ret = waitpid(id, NULL,0);
 27     if (ret == id)  printf("进程等待成功\n");
 28   }
 29   return 0;
 30 }

运行结果:
在这里插入图片描述

execlp()

int execlp(cosnt char *file, const char *argv,);
  • exec + l + p,从而得知是list和path,,得到:
  1. 第一个参数是file,而不是path
  2. 第二个参数是argv而不是argv[]
  3. 第三个参数是…

参数:
file

  • 填入想要执行的程序的文件名即可

argv

  • 命令怎么写,这里的命令行参数就如何填写
  • 解决如何执行程序的问题
    例如:你写的命令是"ls -a -l",那么在命令行参数填写*“ls”, “-a”,“-l”*就行

  • 可变参数,可以传多个参数
    如果你不使用,就填上NULL就行

代码演示

  1 #include<stdio.h>
  2 #include<unistd.h>
  3 #include<stdlib.h>
  4 #include<sys/types.h>
  5 #include<sys/wait.h>
  6 
  7 int main()
  8 {
   
  9 
 10   printf("开始前:\n");
 11 
 12   pid_t id = fork();
 13   if(id == 0)
 14   {
   
 15   execlp("ls", "ls","-l","-a", NULL);
 16   }
 17   else
 18   {
   
 19     pid_t ret = waitpid(id, NULL,0);
 20     if (ret == id)  printf("进程等待成功\n");
 21   }
 22   return 0;
 23 }

运行结果:
在这里插入图片描述

execvp()

int execvp(const char *file, const char *argv[]);
  • exec + v + p,从而得知是vector 和path:
  1. vector,表示第二个参数是argv[]
  2. path,表示第一个参数是file

代码演示

  1 #include<stdio.h>
  2 #include<unistd.h>
  3 #include<stdlib.h>
  4 #include<sys/types.h>
  5 #include<sys/wait.h>
  6 
  7 int main()
  8 {
   
  9 
 10   printf("开始前:\n");
 11 
 12   pid_t id = fork();
 13   if(id == 0)
 14   {
   
 15     //child
 16   char *const myargv[] = {
   
 17     (char*)"ls",
 18     (char*)"-l",
 19     (char*)"-a",
 20     NULL
 21   };
 22   // execv("/usr/bin/ls", myargv); 
 23  // execlp("ls", "ls","-l","-a", NULL);
 24   execvp("ls", myargv);                                                                                                                    
 25   }
 26   else
 27   {
   
 28     pid_t ret = waitpid(id, NULL,0);
 29     if (ret == id)  printf("进程等待成功\n");
 30   }
 31   return 0;
 32 }

运行结果:

在这里插入图片描述

execle()

int execle(const char *path, const char *argv,, char *const envp[]);
  • exec + l + e,是list和environment,得到:
  1. 没有p,说明第一个参数是const char* path,需要填写路径
  2. 有l,说明第二个参数传的argv不是argv[]
  3. 有l,说明第三个参数是…
  4. 有e,说明第四个参数是envp[]环境变量

参数
envp

  • 环境变量

在进行代码演示之前,需要明确:
子进程可以继承父进程的环境变量!程序替换的时候不替换环境变量

如果我们想给子进程传递环境变量,该怎么做呢?

  1. 新增环境变量
  2. 彻底替换环境变量

新增环境变量需要一个函数putenv
在这里插入图片描述

代码演示

在这里插入图片描述

如果没有手动导入,而是使用下面的environ,也就是系统的环境变量,如下图
这是mycmd.c文件
在这里插入图片描述
那么得到的结果是这样的。
可以观察到PATH是可以打印出来的
在这里插入图片描述

如果使用我们自己手动导入的环境变量,例如下图这样
这是mycmd.c文件
在这里插入图片描述
运行结果如下图所示,可以发现,PATH的值为空
在这里插入图片描述

这是otherExc.cpp文件
在这里插入图片描述
这是mycmd.c文件
在这里插入图片描述
调用自己导入的环境变量
在这里插入图片描述
运行结果
在这里插入图片描述
从上面的演示可以得出:添加环境变量给目标进程,是覆盖式的!!!所以最后的环境变量只剩下MYPATH

子进程会继承父进程的环境变量!!!

  • 子进程会继承父进程的环境变量,当父进程调用fork()创建子进程时,子进程会继承父进程的所有环境变量。
  • 当子进程调用execlp()等函数执行其他程序时,子进程也会继承父进程的环境变量。
  • 如果需要在子进程中更改环境变量,可以使用setenv()或putenv()等函数进行更改。
  • 但是,更改的环境变量只会影响当前进程和它的子进程,并不会影响父进程或其他进程的环境变量。

相关推荐

最近更新

  1. TCP协议是安全的吗?

    2024-02-17 04:02:01       18 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-02-17 04:02:01       19 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-02-17 04:02:01       18 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-02-17 04:02:01       20 阅读

热门阅读

  1. Python OpenCV 牛刀小试(练习)

    2024-02-17 04:02:01       30 阅读
  2. 英伟达(NVIDIA)和CUDA

    2024-02-17 04:02:01       34 阅读
  3. 洛谷C++简单题小练习day13—文字处理软件

    2024-02-17 04:02:01       26 阅读
  4. react中render阶段做了什么

    2024-02-17 04:02:01       34 阅读
  5. 作业2.15

    2024-02-17 04:02:01       26 阅读
  6. 开源软件的影响力

    2024-02-17 04:02:01       22 阅读
  7. 洛谷 P2580 于是他错误的点名开始了

    2024-02-17 04:02:01       26 阅读