进程替换出现的背景
我们可以用fork函数来创建子进程,使父子进程同时出现。正所谓子承父业,子进程在刚被创建时和父进程共享数据和代码,但是操作者想让子进程执行一个全新的程序,这个使子进程执行全新程序的操作叫做
进程程序替换。
替换原理
1.1替换的原理和底层
进程不变,仅仅替换当前
进程的
代码和数据的技术,叫做
进程程序替换。并
没有创建新的进程。程序替换本质就是
把程序的代码+数据,加载到特定进程的上下文中。C/C++程序要运行,必须要先加载内存中,如何加载呢?是通过加载器,加载器的底层原理就是一系列的exec*程序替换函数。
用fork创建子进程后,子进程执行的是和父进程相同的程序(但有可能执行不同的代码分支),若想让子进程执行另一个程序,往往需要调用一种exec函数。
当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,并从新程序的启动例程开始执行。
1.2进程替换时创建新进程没?
进程程序替换之后,该进程对应的
PCB、
进程地址空间以及
页表等数据结构都没有发生改变,只是
进程在物理内存当中的数据和代码发生了改变,所以并没有创建新的进程,而且进程程序替换前后该进程的pid并没有改变。
1.3对父进程的代码和数据的影响?
子进程刚被创建时,与父进程共享代码和数据,但当子进程需要进行进程程序替换时,也就意味着子进程需要对其数据和代码进行写入操作,这时便需要
将父子进程共享的代码和数据进行写时拷贝,此后父子进程的代码和数据也就分离了,因此子进程进行程序替换后不会影响父进程的代码和数据。
2.替换函数
提起进程替换,就不得不说到替换函数,替换函数有6种,他们均是以exec开头的,又把他们统称为exec函数。
exec把当前进程映像替换成新的程序文件,而且该新程序通常从main函数开始执行。进程ID并不改变。我们称调用exec的进程为
调用进程(calling process),称新执行的程序为新程序(new program)。
2.1六大函数介绍
#include <unistd.h>`
int execl(const char *path, const char *arg, ...);
int execv(const char *path, char *const argv[]);
int execlp(const char *file, const char *arg, ...);
int execvp(const char *file, char *const argv[]);
int execle(const char *path, const char *arg, ...,char *const envp[]);
int execve(const char *path, char *const argv[], char *const envp[]);
函数参数:
path:用来替换的程序所在的路径file:程序名
arg, …:列表的形式传参
arg[]:数组的形式传参
envp[]:自己维护的环境变量
- 特殊记忆法进行特殊记忆
替换函数接口
参数格式
采用方法
l
list
参数采用列表方式
v
vector
参数采用数组方式
p
path
自动搜索环境变量PATH
e
env
自己维护环境变量,或者说自定义环境变量
函数名
参数格式
是否带路径
是否使用当前环境变量
execl
列表(l)
是(path)
是
execv
数组(v)
是(path)
是
execlp
列表(l)
否
是
execvp
数组(v)
否
是
execle
列表(l)
是(path)
否,自己组装环境变量
execve
数组(v)
是(path)
否,自己组装环境变量
接下来让我们一一介绍一下:
1.execl函数
int execl(const char *path, const char *arg, ...);
第一个参数是要执行程序的路径,
第二个参数是可变参数列表,表示你要如何执行这个程序,
并以NULL结尾。
假如要执行ls指令:
execl("/usr/bin/ls", "ls", "-a", "-i", "-l", NULL);
2.execv函数
int execv(const char *path, char *const argv[]);
第一个参数是要执行程序的路径,
第二个参数是一个指针数组,数组当中的内容表示你要如何执行这个程序,
数组以NULL结尾。
假如要执行ls指令:
char* myargv[] = { "ls", "-a", "-i", "-l", NULL };
execv("/usr/bin/ls", myargv);
3.execlp函数
int execlp(const char *file, const char *arg, ...);
第一个参数是要执行程序的名字,第二个参数是可变参数列表,表示你要如何执行这个程序,并以NULL结尾。
假如要执行的是ls程序。
execlp("ls", "ls", "-a", "-i", "-l", NULL);
4.execvp函数
int execvp(const char *file, char *const argv[]);
第一个参数是要执行程序的名字,
第二个参数是一个指针数组,数组当中的内容表示你要如何执行这个程序,
数组以NULL结尾。
假如要执行ls指令:
char* myargv[] = { "ls", "-a", "-i", "-l", NULL };
execvp("ls", myargv);
5.execle函数
int execle(const char *path, const char *arg, ...,char *const envp[]);
第一个参数是要执行程序的路径,
第二个参数是可变参数列表,表示你要如何执行这个程序,并
以NULL结尾,
第三个参数是你自己设置的环境变量。
假如你设置了MYVAL环境变量,在mycmd程序内部就可以使用该环境变量。
char* myenvp[] = { "MYVAL=2021", NULL };
execle("./mycmd", "mycmd", NULL, myenvp);
6.execve函数
int execve(const char *path, char *const argv[], char *const envp[]);
第一个参数是要执行程序的路径,
第二个参数是一个指针数组,数组当中的内容表示你要如何执行这个程序,
数组以NULL结尾,
第三个参数是你自己设置的环境变量。
假如设置了MYVAL环境变量,在mycmd程序内部就可以使用该环境变量:
char* myargv[] = { "mycmd", NULL };
char* myenvp[] = { "MYVAL=2023", NULL };
execve("./mycmd", myargv, myenvp);
2.2函数的返回值
这些函数如果调用成功,则加载指定的程序并从启动代码开始执行,不再返回。
如果调用出错,则返回-1。
大胆的理解:只要返回值了,那就意味着调用失败了。
2.21六大函数的关系
只有execve才是真正的系统调用,其它五个函数最终都是调用的execve,也就是说其他五个函数实际上是对系统调用execve进行了封装,以满足不同用户的不同调用场景的。
测试实例:
int main()
{
// 自己组装的环境变量
char* myenv[] = {"MYENV=you can see my", NULL};
// 列表形式传参
execl("/usr/bin/ls", "ls", "-l", "-a", NULL);
execp("ls", "ls", "-l", "-a", NULL);
exece("./mycmd", "mycmd", NULL, myenv);
// 数组形式传参
char* const argv[] = {"ls", "-l", "-a", NULL};
execv("/usr/bin/ls",argv);
execvp("ls", argv);
char* const agrv1[] = {"mycmd", NULL};
execve("./mycmd", agrv1, myenv);// 调用自己的程序
}
还有几点需要解释一下:
程序替换成功后,运行完新程序,则程序直接退出;程序替换成功后,原进程没有退出,使用原进程运行新程序
版权归原作者 13KB 所有, 如有侵权,请联系我们删除。