1. 进程替换的概念
进程替换是指在一个正在运行的进程中,用一个新的程序替换当前进程的代码和数据,使得进程开始执行新的程序,而不是原来的程序。
这种技术通常用于在不创建新进程的情况下,改变进程的行为。
我们之前谈到过fork函数,这个函数可以启动一个子进程,子进程继承了父进程的代码和数据。
在谈到进程替换之前,我们只能通过判断fork函数的返回值id来区分父子进程,并让二者运行不同的分支。而利用进程替换技术,我们可以将子进程的代码数据完全替换为另一个程序,实现我们所期望的,父子进程完全独立为两个不同的进程。
进程替换的原理
进程替换的原理涉及到操作系统的内存管理和进程控制。当一个进程调用exec系列函数时,操作系统会将新程序的代码和数据加载到内存中,并将其与当前进程的地址空间相关联。这个过程通常涉及到以下几个步骤:
- 加载新程序:操作系统将新程序的可执行文件从磁盘加载到内存中。
- 替换代码和数据:新程序的代码和数据会替换当前进程的代码和数据段。
- 更新进程状态:进程的状态会被更新,以反映新程序的执行状态。
- 执行新程序:进程开始执行新程序的入口点,通常是main函数。
在这个过程中,进程的标识符(PID)和其他一些属性(如打开的文件描述符、环境变量等)通常会保持不变。
2. exec进程替换函数
在Linux系统中,进程替换通常通过exec系列函数来实现,该系列函数包含在头文件<unistd.h>。
这些函数包括:
- **
execl
**:执行一个新程序,参数以列表形式给出。int execl(const char *pathname, const char *arg, ...);
- **
execlp
**:执行一个新程序,参数以列表形式给出,并在环境变量PATH中搜索程序。int execlp(const char *file, const char *arg, ...);
- **
execle
**:执行一个新程序,参数以列表形式给出,并提供自定义的环境变量。int execle(const char *pathname, const char *arg, ...);
- **
execv
**:执行一个新程序,参数以数组形式给出。int execv(const char *pathname, char *const argv[]);
- **
execvp
**:执行一个新程序,参数以数组形式给出,并在环境变量PATH中搜索程序。int execvp(const char *file, char *const argv[]);
- **
execve
**:执行一个新程序,参数以数组形式给出,并提供自定义的环境变量。int execve(const char *pathname, char *const argv[], char *const envp[]);
- **
execvpe
**:执行一个新程序,参数以数组形式给出,并提供自定义的环境变量。int execvpe(const char *file, char *const argv[], char *const envp[]);
这些函数的使用方式和参数传递方式略有不同,但它们的基本功能都是相同的:用新程序替换当前进程的代码和数据。
记忆技巧:
- l(list):表示参数采用列表。
- v(vector):参数用数组。
- p(path):到环境变量PATH中搜索指定程序,无需完整路径(带p的函数第一个参数为file,代表可执行程序;不带p的函数第一个参数为pathname,代表完整路径)。
- e(env) : 表示自定义环境变量,不带e的表示继承当前的环境变量。
使用示例:
#include <stdio.h>
#include <unistd.h>
int main()
{
char* vector[] = {"ls", "-l", "-a", NULL};
int id = fork();
if(id == 0)
{
execvp("ls", vector);
return 0;
}
int pid = wait(NULL);
return 0;
}
** 注意:传入的参数为命令行参数,也就是说在命令行要执行该程序需要输入什么,参数就传递什么,主要是不要忘记选项是从第二个参数开始的。**
第一个参数传什么都不要紧,随你喜欢,但要记得传:
#include <stdio.h>
#include <unistd.h>
int main()
{
char* vector[] = {"cxk", "-l", "-a", NULL};
int id = fork();
if(id == 0)
{
execvp("ls", vector);
return 0;
}
int pid = wait(NULL);
return 0;
}
execve函数
该函数相比于其他函数具有一定的特殊性,他是上述函数中唯一一个系统调用。
在命令行输入[man exec]能查到如下信息,可以看到并没有execve的存在,且这些函数都在3号手册当中:
只有单独查询execve函数时才能查到,可以看到该函数在2号手册(系统调用接口)中:
** execve函数的特殊性:**
- 系统调用层级的基础地位-
execve
在exec
函数族中具有特殊的基础性地位。它是直接与系统调用接口紧密相连的函数。其他的exec
系列函数(如execl
、execv
等)在很多情况下最终可能会调用execve
来实现实际的进程替换操作。- 例如,在一些库函数的实现中,为了提供更方便的参数传递方式(如execl
的可变参数列表形式),可能会在内部对参数进行处理后调用execve
来完成进程替换的核心功能。- 参数处理方式的不同-
execve
的参数包含要执行的程序文件路径、传递给新程序的命令行参数数组以及环境变量数组。这种参数形式与其他exec
函数有所不同。- 像execl
函数,它的参数是以可变参数列表的形式,最后以NULL
结尾,这种形式在使用上有一定的便利性,但在底层实现中可能需要更多的转换工作才能与系统调用接口对接,而execve
的参数形式更直接地反映了系统调用的需求。- 安全和权限方面的考虑- 由于
execve
是直接进行进程替换的底层函数,在安全和权限管理方面有着重要的作用。它对可执行文件的路径、执行权限等有着严格的要求。- 当调用execve
时,系统会根据文件的权限设置(如是否可执行、所属用户和组等)以及当前进程的权限来判断是否允许进程替换操作。这种严格的权限检查有助于保障系统的安全性。- 与内核交互的特点-
execve
在与内核交互时,需要将新程序的代码和数据加载到当前进程的地址空间,同时更新进程的各种状态信息,如程序计数器、堆栈指针等。这个过程涉及到内核中的进程管理、内存管理等多个模块的协同工作。- 相比其他exec
函数,execve
在与内核的这种深度交互方面更为直接,因为其他函数可能会在调用execve
之前进行一些额外的参数处理或环境设置。
exec函数族调用关系如下:
版权归原作者 大筒木老辈子 所有, 如有侵权,请联系我们删除。