0


【Linux】---进程程序替换

进程替换出现的背景
我们可以用fork函数来创建子进程,使父子进程同时出现。正所谓子承父业,子进程在刚被创建时和父进程共享数据和代码,但是操作者想让子进程执行一个全新的程序,这个使子进程执行全新程序的操作叫做
进程程序替换。

  1. 替换原理

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);// 调用自己的程序
}

还有几点需要解释一下:

程序替换成功后,运行完新程序,则程序直接退出;程序替换成功后,原进程没有退出,使用原进程运行新程序

标签: linux 运维 服务器

本文转载自: https://blog.csdn.net/bit_jie/article/details/128775325
版权归原作者 13KB 所有, 如有侵权,请联系我们删除。

“【Linux】---进程程序替换”的评论:

还没有评论