目录
进程的程序替换
0.相关函数
1.先看现象
2.解释原理
3.将代码改成多进程版
4.使用其它的替换函数,并且认识函数参数的含义
5.其它
进程的程序替换
0.相关函数
关于进程替换我们需要了解的6个函数:
函数解释:
- 这些函数如果调用成功则加载新的程序从启动代码开始执行,不再返回。
- 如果调用出错则返回-1
- 所以exec函数只有出错的返回值而没有成功的返回值
命名理解:
- l(list) : 表示参数采用列表
- v(vector) : 参数用数组
- p(path) : 有p自动搜索环境变量PATH
- e(env) : 表示自己维护环境变量
exec调用举例如下:
int main() { char* const argv[] = { "ps", "-ef", NULL }; char* const envp[] = { "PATH=/bin:/usr/bin", "TERM=console", NULL }; execl("/bin/ps", "ps", "-ef", NULL); // 带p的,可以使用环境变量PATH,无需写全路径 execlp("ps", "ps", "-ef", NULL); // 带e的,需要自己组装环境变量 execle("ps", "ps", "-ef", NULL, envp); execv("/bin/ps", argv); // 带p的,可以使用环境变量PATH,无需写全路径 execvp("ps", argv); // 带e的,需要自己组装环境变量 execve("/bin/ps", argv, envp); exit(0); }
1.先看现象
先来看一段代码:
运行结果:我们发现让程序使用exec*函数,可以执行起来新的程序
我们还发现,执行玩这条语句,后面的语句都不执行了
2.解释原理
我们都知道,我们的可执行程序运行起来就变成了进程。
创建进程=创建内核相关管理的数据结构(task_struct,mm_struct,页表)+代码和数据。
内存管理的数据,如代码段、数据段和堆栈等,则包含了程序执行所需的数据和代码。
当我们谈论进程替换时,实际上是指用一个新的程序来完全替代当前进程的内存映像,也就是替换掉原有的代码段、数据段和堆栈等内存管理的数据。而CPU执行的上下文,如程序计数器、寄存器和程序状态字等,则会被新的程序所继承。这样,新的程序就可以从它的入口点开始执行,就像它是一个全新的进程一样。(但他不是一个全新的程序,因为这个过程并没有创建新的进程,只是替换掉了原进程的数据和代码,依旧使用原来进task_struct,mm_struct,页表) 用老进程的壳子执行新的代码和数据。(可以理解成被夺舍了)
exec*函数族就是用来实现这种进程替换的系统调用。这些函数会根据指定的文件名找到可执行的文件,并用它来替换当前进程的内容。换句话说,它们会在当前进程内部执行一个全新的可执行文件。
当你调用一个exec*函数时,它会做以下几件事情:
- 查找并加载新的程序文件到内存。
- 清除当前进程的内存映像,包括代码段、数据段和堆栈等。
- 将新的程序的代码和数据加载到当前进程的内存空间中。
- 更新程序计数器,使其指向新程序的入口点。
- 开始执行新程序。
值得注意的是,exec*函数执行成功就不需要关心它的返回值了。这是因为调用进程的实体,包括代码段、数据段和堆栈等,都已被新的内容所取代,只留下进程ID等一些表面上的信息保持原样。所以,一旦exec*函数执行成功,当前进程就已经被新的程序所替代,原有的程序已经不再存在。(这就是为什么在上面执行那段代码的时候,程序最后没有执行printf("test .... end!\n");)
总的来说,exec*函数族是Linux环境下实现进程替换的重要工具。它们通过替换当前进程的内存映像来执行新的程序,使得我们可以在一个进程中执行不同的程序,从而实现程序的切换和执行流程的改变。
3.将代码改成多进程版
我想进行程序替换,但我不想影响我父进程本身,这时候我们就可以把代码改成多进程版本,让子进程去进行替换,父进程进行wait就行了。
代码示例:
运行结果:
4.使用其它的替换函数,并且认识函数参数的含义
int execl(const char *path, const char *arg, ...);
path : 我们要执行程序,需要带路径(怎么找到程序,你得告诉我)(你想执行谁)
list列表:在命令中怎么执行,就这么传参(标准情况)(你想怎么执行)
int execv(const char *path, char *const argv[])
path :和上面的一样argv:是一个指针数组,直接把参数传到数组里
用法:
int execlp(const char *file, const char *arg, ...);
int execvp(const char *file, char *const argv[]);
p:用户可以不传要执行的文件路径(但是要穿文件名),查找这个程序,系统会自动在环境变量PATH中进行查找。用法:
int execle(const char *path, const char *arg, ...,char *const envp[]);
int execvpe(const char *path, char *const argv[], char *const envp[]);
这里的e:表示环境变量(environment)上面的程序替换,我们替换的都是系统命令,可不可以替换我们自己的程序呢?
这是我们在C语言程序调用C++程序的方式
我们在C++程序中写的
编译c和c++程序运行c程序:我们成功的替换了我们自己的程序
接着谈环境变量。接上面代码:这一次我们将环境变量给了execvpe函数
我们在c++程序中接收了,命令行参数表和环境变量表并将它们打印出来了:
编译运行看一下效果:
结论:我们平时自己运行的程序,命令行参数和环境变量,是父进程通过将自己的环境变量表和命令行参数表通过,execvpe函数传递来的。而我的父进程本身就有一批环境变量!!从bash来。(我们是可以将bash的环境变量表直接传给子进程的,就不做演示了)。
5.其它
事实上,只有execve是真正的系统调用,其它五个函数最终都调用 execve,所以execve在man手册 第2节,其它函数在man手册第3节。这些函数之间的关系如下图所示。
下图exec函数族 一个完整的例子: