文章目录
- 前言
- 函数的介绍及使用
-
- [execl( )](#execl( ))
- execlp()
- execle()
- execv()
- [execvp()](#execvp())
- execvpe()
- execve(系统调用)
- 拓展
前言
在现代操作系统中,进程是执行程序的基本单位,每个进程拥有独立的地址空间、代码和数据。当一个程序正在运行时,有时我们希望它能够"脱胎换骨",从自身变成另一个程序------这就是程序替换(Process Replacement)的概念。在 Unix/Linux 系统中,这种能力被 exec 系列函数实现,它允许一个进程在原地加载并执行新的可执行文件,而无需创建新进程,从而节省资源并保持进程的身份标识(PID)不变。
exec系列函数关系图
lua
+--> 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结尾,表示参数传入完毕)
代码示例:
c
#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基本相同)
c
execlp("ls","ls","-l","-a",NULL);
execle()
指定文件绝对路径,自定义环境变量
原型: int execle(const char *path, const char *arg, .../*, (char *) NULL, char * const envp[] */);
参数说明:
path:文件绝对路径arg:可变参数列表envp:自定义的环境变量(注意:必须写在NULL的后面)
代码示例:
c
//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;
}
c
//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;可看作可变参数列表)
代码示例:
c
#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: 自定义命令行参数
代码示例:
c
#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:自定义环境变量
代码示例:
c
//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");
}
c
//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的后面,就不会改变当前进程原有的环境变量了;
代码示例:
c
//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;
}
c
//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为空字符串或者字符串中包含=,就会报错;
本篇文章就到此结束了,如果本文对你有帮助,麻烦你 👍点赞 ⭐收藏 ❤️关注 吧~