【Linux系统编程】程序替换:execve(execl、execlp、execle、execv、execvp、execvpe)
文章目录
- 前言
- exec系列函数关系图
- 程序替换的原理
- 函数的介绍及使用
- execl( )
- execlp()
- execle()
- execv()
- execvp()
- execvpe()
- execve(系统调用)
- 拓展
- putenv
- setenv / unsetenv
- setenv
- unsetenv
前言
在现代操作系统中,进程是执行程序的基本单位,每个进程拥有独立的地址空间、代码和数据。当一个程序正在运行时,有时我们希望它能够“脱胎换骨”,从自身变成另一个程序——这就是程序替换(Process Replacement)的概念。在 Unix/Linux 系统中,这种能力被 exec 系列函数实现,它允许一个进程在原地加载并执行新的可执行文件,而无需创建新进程,从而节省资源并保持进程的身份标识(PID)不变。
exec系列函数关系图
+--> execl() —— 参数用列表|execve() <------+--> execv() —— 参数用数组|+--> execlp() —— 查 PATH|+--> execvp() —— 查 PATH,参数用数组|+--> execle() —— 自定义环境|+--> execvpe() —— 查 PATH + 自定义环境
- 返回值:成功则不返回(当前程序被覆盖);失败返回
-1 exec*系列函数都是对系统调用接口execve()的封装,封装函数的形参中未实现的则使用系统默认的值;- 例如: execve接口的参数有
pathname、argv、envp;execl的形参只有两个,path和argv;那么一个进程的程序数据使用execl替换后,在替换的程序中访问argv,那就是我显示传入的形参argv的值,
但是如果访问envp,那么就是系统默认传入的envp值(environ指向的环境变量);
- 例如: execve接口的参数有
- 函数的命名规律:
exec[l|v][p][e]l:list(可变参数列表)v:vector(数组)p:path(PATH查找)e:environ(自定义环境)
程序替换的原理
- 可执行文件=代码+数据
- 在一个进程在执行
exec*替换函数时,会将当前进程的代码段、数据段、堆、栈都被清空,并初始化新的堆、栈(argv,envp)、数据和代码内容; - 程序替换会将当前进程的代码和数据替换为其他文件的代码数据,所以我们一般会创建一个子进程,将其他文件的代码和数据替换到子进程中,进而不影响父进程的正常运行;
函数的介绍及使用

execl( )
函数原型: int execl(const char *path, const char *arg, ... /* (char *) NULL */);
参数说明:
path:文件的绝对路径arg:可变参数列表(最后以NULL结尾,表示参数传入完毕)
代码示例:
#include <unistd.h>
#include <stdio.h>int main() {printf("当前程序开始运行……\n");if(fork()==0){execl("/usr/bin/ls","ls","-l","-a",NULL);exit(1);//如果返回并继续向下执行,说明替换失败,直接退出;}waitpid(-1,NULL,0);//进程等待printf("当前程序停止运行……\n");
}
执行结果:

execlp()
再execl基础上不仅可以通过文件名方式(环境变量PATH)查找还可以通过绝对路径的方式查找
原型:int execlp(const char *file, const char *arg, .../* (char *) NULL */);
参数说明:
file:文件名或文件绝对路径;arg:可变参数列表(最后以NULL结尾,表示参数传入完毕)
代码示例:(与execl基本相同)
execlp("ls","ls","-l","-a",NULL);
execle()
指定文件绝对路径,自定义环境变量
原型:int execle(const char *path, const char *arg, .../*, (char *) NULL, char * const envp[] */);
参数说明:
path:文件绝对路径arg:可变参数列表envp:自定义的环境变量(注意:必须写在NULL的后面)
代码示例:
//test.c
#include <sys/wait.h>
#include <unistd.h>
#include <stdio.h>int main() {printf("当前程序开始运行……\n");if(fork()==0){printf("hello test,child pid:%d\n",getpid());char* env[]={ //自定义环境变量"PATH=/learn/linux/leason15",NULL};execle("proc","./proc",NULL,env);}waitpid(-1,NULL,0);printf("当前程序停止运行……\n");return 0;
}
//proc.c
#include <stdio.h>
#include <unistd.h>int main(int argc,char* argv[],char* env[])
{printf("hello proc,child pid:%d\n",getpid());for(int i=0;env[i];i++)printf("env:%s\n",env[i]);return 0;
}
运行结果:

- 这里可以看到
test.c的子进程将proc文件进行替换;并在替换前和替换后输出PID,可以证明程序替换后确实没有创建新的进程;
execv()
指定绝对路径,自定义命令行参数
原型:int execv(const char *path, char *const argv[]);
参数说明:
path:文件绝对路径argv: 自定义命令行参数- (指针数组,数组与系统命令行参数格式相同,最后一个参数为NULL;可看作可变参数列表)
代码示例:
#include <sys/wait.h>
#include <unistd.h>
#include <stdio.h>int main() {printf("当前程序开始运行……\n");if(fork()==0){char* argv[]={"ls","-l",NULL};execv("/usr/bin/ls",argv); }waitpid(-1,NULL,0);printf("当前程序停止运行……\n");return 0;
}
运行结果:

execvp()
与execv基本相同,唯一不同的是 可以通过文件名(PATH环境变量)的方式查找文件;
原型:int execvp(const char *file, char *const argv[]);
参数说明:
file:要执行文件名/绝对路径argv: 自定义命令行参数
代码示例:
#include <sys/wait.h>
#include <unistd.h>
#include <stdio.h>int main() {printf("当前程序开始运行……\n");if(fork()==0){char* argv[]={"ls","-l",NULL};execvp("ls",argv); //通过PATH查询}waitpid(-1,NULL,0);printf("当前程序停止运行……\n");return 0;
}
execvpe()
相当于execvp和execle的结合版;
原型:int execvpe(const char *file, char *const argv[],char *const envp[]);
参数说明:
file:要执行文件名/绝对路径argv: 自定义命令行参数envp:自定义环境变量
代码示例:
//test.c
#include <sys/wait.h>
#include <unistd.h>
#include <stdio.h>int main() {printf("当前程序开始运行……\n");if(fork()==0){char* argv[]={"-a","-b","-c",NULL};char* envp[]={"PATH=/1/2/3","NAME=YANG",NULL};execvpe("./proc",argv,envp); }waitpid(-1,NULL,0);printf("当前程序停止运行……\n");
}
//proc.c
#include <stdio.h>
#include <unistd.h>int main(int argc,char* argv[],char* env[])
{printf("hello proc,child pid:%d\n",getpid());for(int i=0;env[i];i++)printf("env:%s\n",env[i]);printf("argv:");for(int i=0;argv[i];i++)printf("%s ",argv[i]);printf("\n");return 0;
}
运行结果:

execve(系统调用)
前面的六个程序替换函数底层都是调用的
execve接口;这里execve函数的参数和前面替换函数的用法相同,这里就简单说明每个参数的含义即可,了解完前面的函数自然就会使用这个函数接口

函数原型:int execve(const char *pathname, char *const argv[], char *const envp[]);
参数说明:
pathname:文件的绝对路径argv[]:命令行参数数组(最后以 NULL 结束)envp[]:环境变量数组(同样以 NULL 结束)
拓展
putenv
作用: 把指定的字符串指针直接放入进程的环境变量表(char** environ)中
返回值:
- 成功返回
0 - 失败返回
非 0(通常是内存不足)
参数:
string:字符串 (是一个形如 "NAME=VALUE" 的字符串)
特点: 直接将你传入的指针地址放到当前进程的环境变量表中;(也就意味着后续你如果修改这段字符串的内容,环境变量中的这个值也会随之改变)
在使用程序替换函数中envp参数时,都是我们自定义的;但是这里会有一个小问题:
- 在我们还没对一个进程中的代码数据进行替换时,它的环境变量等参数都是从父进程继承下来的,也就是系统原本的环境变量,当我们传入自定义的
envp参数之后,系统的环境变量表中的值也会被全部替换,在实际情况中,系统的环境变量中的参数我们也需要用到,自定义的环境变量参数也要用到;所以这里就需要用putenv函数来将我们自定义的环境变量参数插入到系统的环境变量表中,这样就不会覆盖掉原本的环境变量了; char** environ参数: 全局变量,指向当前进程的环境变量表;- 自定义的环境变量传给
envp时,我们就可以通过putenv将自定义环境变量中的值插入到environ的后面,就不会改变当前进程原有的环境变量了;
代码示例:
//test.c
#include <sys/wait.h>
#include <unistd.h>
#include <stdio.h>int main() {printf("当前程序开始运行……\n");if(fork()==0){char* argv[]={"-a","-b","-c",NULL};char* envp[]={"TEST_PATH=/1/2/3",//避免与进程环境变量中的PATH重名,导致PATH的value值"NAME=YANG",NULL};extern char** environ; for(int i=0;envp[i];i++)putenv(envp[i]); //将自定义环境变量中的值插入到当前进程的环境变量中execvpe("./proc",argv,environ); //最后envp参数直接传入当前进程的环境变量environ}waitpid(-1,NULL,0);printf("当前程序停止运行……\n");return 0;
}
//proc.c
#include <stdio.h>
#include <unistd.h>int main(int argc,char* argv[],char* env[])
{for(int i=0;env[i];i++)printf("env:%s\n",env[i]);printf("argv:");for(int i=0;argv[i];i++)printf("%s ",argv[i]);printf("\n");return 0;
}
运行结果:

setenv / unsetenv
setenv和putenv的作用基本相同;
在底层setenv会在堆上重新分配environ的空间并拷贝字符串,将参数设置(插入)到拷贝后的空间中;之后我们如果修改原来的字符串并不会影响到环境变量中的参数,相对putenv更加的安全,只是不断的拷贝性能会有所下降;(在实际编码时,更推荐使用setenv)
setenv
原型:int setenv(const char *name, const char *value, int overwrite);
参数说明:
name:环境变量名,不能包含=value:环境变量值overwrite:- 若为
0,且环境变量已存在,则不修改; - 若为
非 0,则强制更新;
- 若为
返回值
- 成功返回
0 - 失败返回
-1
unsetenv
作用: 删除指定名称的环境变量;
原型:int unsetenv(const char* name)
返回值:
- 成功返回
0 - 失败返回
-1
注意: 如果name在环境变量中不存在,unsetenv什么也不做;如果name为空字符串或者字符串中包含=,就会报错;
本篇文章就到此结束了,如果本文对你有帮助,麻烦你 👍点赞 ⭐收藏 ❤️关注 吧~
