fork创建子进程以后,子进程继承父进程的一部分代码和数据,如果我们希望子进程和父进程执行不同的代码,我们可以通过判断 fork函数 的返回值来判断当前进程是父进程还是子进程,进而分配不同的任务。
exec 函数族的作用是替换原本要执行程序,转而去执行其他程序,可以让我们更加灵活的给子进程分配任务。
目录
一、exec函数族
1、execl / execlp
(1) execl函数参数解析
(2) execlp 函数参数解析
2、execv / execvp / execvpe
(1) execv函数参数解析
(2) execvp函数参数解析
(3) execvpe函数参数解析
二、exec 函数族的应用场景
三、进程程序替换原理
exec函数族的命名存在规律性,exec之后的字母代表了当前函数传递参数的类型。
execl 和 execlp函数的声明如下:
execl函数是去指定路径下面搜索并执行相应的程序。execl 的最后一个参数必须是NULL
// argc —— 接收到的外部参数的个数
// argv —— 接收到的外部参数的内容
int main(int argc, char** argv){}
// execl
const char* path = "/bin/ls";
if(execl(path, "ls", "-a", "-l", NULL)<0){ // execl 最后一个参数必须是NULL
perror("execl");
}
execlp函数是去当前Shell 的环境变量PATH中搜索并执行相应的程序。execlp 的最后一个参数必须是NULL
// execlp
// 环境变量PATH包含了/bin,程序可以在bin目录下找到 ls文件
const char* file= "ls";
if(execl(file, "ls", "-a", "-l", NULL)<0){ // execl 最后一个参数必须是NULL
perror("execlp");
}
execv 的作用和 execl 的作用是一样的,execv中的“v”表示vector,传递的是一个数组;execl中的“l”表示list,传递的是参数列表。两者的区别仅仅只是传递参数的方式不同。
execv函数是去指定路径下面搜索并执行相应的程序,传入的是一个参数数组,而不是参数列表。
// execv
const char* path = "/bin/ls";
char* argv[] = {"ls", "-a", "-l", NULL}; // argv 最后一个参数必须是NULL
if(execv(path, argv)<0){
perror("execv");
}
execvp函数是去当前Shell 的环境变量PATH中搜索并执行相应的替换程序。
// execv
// 环境变量PATH包含了/bin,程序可以在bin目录下找到 ls文件
const char* file= "ls";
char* argv[] = {"ls", "-a", "-l", NULL}; // argv 最后一个参数必须是NULL
if(execvp(file, argv)<0){
perror("execvp");
}
execvpe函数比execvp函数多了一个“e”,“e”表示environment,execvp函数是在环境变量PATH中搜索并执行替换程序;而execvpe函数则是使用自定义的环境变量,也就是相当于我们自己提供替换程序的搜索路径(搜索路径可以有多个)
// execvpe
const char* file= "ls";
char* argv[] = {"ls", "-a", "-l", NULL}; // argv 最后一个参数必须是NULL
char* const envp[]={"PATH=/bin:/usr/bin", "TERM=console", NULL}; //打包成数组
if(execvpe(file, argv, envp) < 0) {
perror("execvpe");
}
exec函数执行的时候有如下特点:
如果是单进程调用了exec函数,那么exec函数之后的代码都不会被执行。
如果是父子进程中,让子进程调用了 exec 函数,那么子进程就会转而去执行 exec函数指向的程序,但是父进程不会受到影响。
一个完整的进程 = 进程控制块(task_struct) + 虚拟内存 + 页表 + 物理内存中的代码和数据
由这个图可以知道,进程程序替换,替换的是物理内存中的代码段和数据段的内容
现在假设磁盘上有一段程序B
我们要用程序B的代码和数据去替换上面这个进程的代码和数据
修改的只是物理内存中的代码和数据,PCB、虚拟内存并没有受到影响,所以这里我们需要知道的是,进程程序替换不会创建新的进程!!!