进程的程序替换——exec系列函数的使用
目录
前言
一、替换函数
二、程序替换的本质
一些细节:
三、程序替换与环境变量间的关系
1.介绍其他参数的意义并总结
2.自定义环境变量
1)通过execcle传参全局环境变量
2)通过execcle传参自定义环境变量
3)将自定义环境变量通过putenv导入全局环境变量后
3.execve系统调用
最后有个小问题,当我们通过子进程实现进程替换是时,是先调用的main函数,还是先调用的exec*函数呢?
总结
前言
我们创建子进程的目的无外乎两种:
①是想让子进程执行父进程代码的一部分,比如同时运行父进程的if -else if 条件语句:
②想让子进程执行一个磁盘中的全新的程序。
进程程序替换谈的就是如何让子进程执行一个全新的程序。
一、替换函数
为了让子进程执行新的程序,Linux系统为我们提供了一些相关函数:exec系列函数,如下图所示。提供这么多函数的目的是满足在不同的使用场景下的使用需求,但他们的核心逻辑是一样的。
让我们先以execl函数为例,解释这些参数都有哪些含义。
首先要明白的是,运行一个新的程序,得满足两个步骤:先得在磁盘中找到指定程序加载到磁盘中,其次是如何执行,会带哪些选项。
const char * path:path就记录着新程序在磁盘中的位置,即路径。
const char *arg:arg则记录着用户想要执行得选项有哪些,所以后面跟有“……”,因为选项有时很多,如ls -a -l指令。但值得注意的是参数列表需以NULL结尾。
execl运行失败则返回-1,成功则无返回值。
上述看着有些有些抽象,笔者结合fork、waitpid等编写一段代码以实现ls指令先供笔者参考,好在脑中有个概念。
在子程序实现中运行ls -a -l指令:
注意在命令行怎么写的指令,在execl中就怎么传。
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>//exit
#include<sys/types.h>//waitpid
#include<sys/wait.h>//waitpid
int main()
{pid_t id=fork();if(id<0){perror("error fork:\n");exit(1);//判断子进程是否创建成功}if(id==0){//子进程execl("/usr/bin/ls","ls","-a","-l",NULL);exit(1);//如果运行到这里说明execl运行出错}int status=0;waitpid(id,&status,0);if(WIFEXITED(status)){printf("child exit code:%d\n",WEXITSTATUS(status));}else{printf("error waitpid\n");}return 0;
}
二、程序替换的本质
我们知道,进程在内存中有着task_struct(PCB)结构体,用于记录进程代码与数据存在于物理地址的何处。而程序替换的本质,就是将指定程序的代码和数据加载到子进程的物理地址,将原代码和数据覆盖。
换句话说,进程替换只替换内存中的代码和数据,而与task_struct页表等内核数据结构无关。所以进程替换并没有创建新进程,而是在原进程基础上的替换操作。
一些细节:
1)值得注意的是:在execl函数之后的代码是不会执行的,如上述代码中execl之后的exit(1),因为原代码和数据已经被替换了,除非execl执行失败。这也是为什么进程替换一般通过子进程去做的原因。
2)现在也便能理解为什么exec系列函数执行成功后无返回值,因为没有意义——源代码已经被替换了。
3)进程替换并不会影响父进程,由于写实拷贝保证的进程独立性,当执行execl时会拷贝立即发生写实拷贝。
三、程序替换与环境变量间的关系
1.介绍其他参数的意义并总结
exec系列的函数,他们的核心逻辑是一样的,但各参数不同,原因是为满足在不同的使用场景下的使用需求。
如execvp函数
file参数:如果该程序在环境变量PATH中有记录,那么只用传程序名即可。
对环境变量有疑的可参考:进程优先级介绍,详解环境变量,详解进程地址空间-CSDN博客
argv【】数组:将所有执行参数,放入数组中统一传递。
2.自定义环境变量
经过上述总结,其他的参数读者大概已经有数,那说半天程序替换和环境变量在哪有直接联系呢?
如上图execle中的参数,前俩个参数已经介绍过了,第三个参数envp[ ]的作用是:导入环境变量
这个环境变量可以是:
①系统的全局环境变量
②是父进程自定义的环境变量
③也可以通过putenv将自定义环境变量导入到全局环境变量中实现一起使用
下面分别介绍上述三种情况。
1)通过execcle传参全局环境变量
这里笔者先创建了一个myproc程序:
然后将上面execl的示例程序中的execl改成execle,用execle替换上述写的myproc程序。
然后执行结构是:
可以看到PATH全局环境变量打印出了,自定义环境变量没打印
2)通过execcle传参自定义环境变量
这里在函数内部创建了一个MYENV的数组,并传给了execle函数
执行结果:
可以看到PATH全局环境变量没打印,自定义环境变量打印了
3)将自定义环境变量通过putenv导入全局环境变量后
执行结果:
可以看到全局环境变量与自定义环境变量都打印出来了
总结:
到这里exec*函数系列的所有参数都讲解完毕,使用时具体用哪个,完全看使用环境选择合适参数
3.execve系统调用
上面的exec*系列的函数其实都是基于execve系统调用做的封装,由于系统自带的程序替换execve功能单一,于是C库通封装给出了六个在不同场景下使用的exec*系列函数,使用户有更多的选择性。
最后有个小问题,当我们通过子进程实现进程替换是时,是先调用的main函数,还是先调用的exec*函数呢?
答案是先调用的exec*函数!
让我们回忆一下程序执行的过程,首先是将磁盘中的可执行文件的代码调入内存,创建PCB等内核数据结构对吧?那问题是系统是怎么调的呢,其实系统正是通过execve系统调用加载的,execve本质就是个加载器,只有先将代码调入内存,才谈得上执行,故exec*系类函数先于main函数执行。
所有main函数的3个参数,其实是exec*系列的函数传给它的!
关于main函数的三个参数,笔者之前的文章有提过,感兴趣的读者可以点击下方链接查看:
进程优先级介绍,详解环境变量,详解进程地址空间-CSDN博客
总结
本文主要通过介绍exec系列函数的使用方法解释了什么是进程的程序替换,最后还解释main函数的三个参数都是由exec系列函数传入的概念。
看完点赞,手留余香~