在 Linux 编程中,exec 系列函数是实现进程程序替换的关键手段。所谓“程序替换”,是指当前进程用另一个新程序替换自身的执行映像,同时保留进程号(PID)等资源,达到类似“变身”的效果。这篇文章将介绍 exec 家族的常见成员:execl、execlp、execv、execvp、execvpe 和 execve,并提供对应的代码示例。
exec 函数族基础
exec 家族的函数其实是一类系统调用的多种封装,主要参数如下:
- l:代表参数以列表(List)方式传递(不定参数)。
- v:代表参数以数组(Vector)方式传递。
- p:表示查找程序时会使用 PATH 环境变量。
- e:允许指定新程序的环境变量。
- execve:是内核提供的最底层版本,所有其他 exec 最终都会调用它。
一、execl
execl 是 Linux/Unix 系统中用于进程程序替换(Program Replacement) 的系统调用族之一,属于 exec 系列函数。它的主要作用是用一个新的程序替换当前进程的镜像,从而让当前进程执行一个新的程序。这个过程是不可逆的 —— 替换发生后,原来的程序的后续代码不再继续运行,除非替换失败。
1.1 基本概念
execl 是 execve 的封装版本,简化了程序参数和环境变量的传递方式。使用 execl 后,当前进程的 代码段、数据段、堆栈段 都会被新程序替换。替换后,进程 ID (PID) 不变,但原程序的内容完全被新程序替代。
1.2 函数原型
int execl(const char *path, const char *arg0, ..., (char *)NULL);
参数说明:
参数 | 说明 |
---|---|
path | 要执行的程序路径(需要是绝对路径或相对路径) |
arg0 | 新程序的第一个参数,约定俗成为程序名 |
… | 后续参数,每一个参数都是一个字符串指针 |
NULL | 参数结尾必须以 (char *)NULL 结束,用于标识参数列表结束 |
1.3 使用场景
当你想要在当前进程中加载并执行另一个程序,并直接传递命令行参数时,可以使用 execl,其优点是书写清晰,适合参数固定时使用。
1.4 代码示例
假设要执行ls -l,定义execl_example.cpp文件。
#include <unistd.h>
#include <stdio.h>
int main() {
printf("Before execl\n");
// 执行 /usr/bin/ls,并列出当前目录的文件
// 命令行写法是:ls -l,那么传参时第一个是程序名ls,第二个是-l
execl("/usr/bin/ls", "ls", "-l", (char *)NULL);
// 如果 execl 成功,以下语句不会执行
printf("After execl\n");
return 0;
}
编译时运行
g++ execl_example.cpp -o execl_example
./execl_example
输出结果:
Before execl
total 8
-rwxr-xr-x 1 user user 8320 May 7 10:00 a.out
-rwxr-xr-x 1 user user 8320 May 7 10:01 execl_example
可以看到 execl 成功执行 ls -l,原进程被替换,“After execl” 这行没有输出。
二、execlp
2.1 基本概念
execlp 是 Linux/Unix 系统中 exec 系列函数的一员,它与 execl 类似,都是用于进程程序替换。不同的是:execlp 可以自动搜索环境变量 $PATH 中的路径 来查找要执行的程序,不必提供程序的绝对路径。
2.2 函数原型
int execlp(const char *file, const char *arg0, ..., (char *)NULL);
2.3 参数说明
参数 | 说明 |
---|---|
file | 要执行的程序名(无须写绝对路径,系统会自动到 $PATH 中查找) |
arg0 | 新程序的第一个参数,约定俗成为程序名 |
… | 后续参数,每一个参数都是一个字符串指针 |
NULL | 参数列表的终止标志,必须以 (char *)NULL 结束 |
2.4 使用场景
当你想执行的程序不在当前目录,但你不想写全路径时,使用 execlp 更方便。例如:想执行 ls,而不想写 /bin/ls。
2.5 代码示例
#include <unistd.h>
#include <stdio.h>
int main() {
printf("Before execlp\n");
// 不指定完整路径,直接使用程序名
execlp("ls", "ls", "-a", (char *)NULL);
// 如果 execlp 成功,这里不会被执行
printf("After execlp\n");
return 0;
}
编译:
g++ execlp_example.cpp -o execlp_example
./execlp_example
输出为:
Before execlp
. .. execlp_example
2.6 注意事项
- 与 execl 相比,execlp 会查找 $PATH,因此路径不对的可能性更低。
- 如果命令名拼错(如写成 “lsl”),execlp 会返回 -1 并设置 errno。
- 与其他 exec 函数一样,成功执行后不再返回,失败时可以用 perror() 报错:
三、execv
3.1 基本概念
execv 是 Linux/Unix 系统中 exec 系列函数之一,和 execl 一样可以执行程序替换,但不同之处在于它使用的是一个参数数组而不是可变参数列表。这让它在参数较多或不确定时更灵活和安全。
3.2 函数原型
int execv(const char *path, char *const argv[]);
参数说明:
参数 | 说明 |
---|---|
path | 要执行的程序的完整路径(例如 /usr/bin/ls ) |
argv | 参数数组,必须以 NULL 结尾,第一个元素通常是程序名 |
即:
argv[0] 是程序名(约定俗成)
argv[1]…argv[n] 是程序的其他参数
argv[n+1] == NULL 表示参数数组结束
3.3 使用场景
适合程序参数来源于运行时输入、配置文件、逻辑拼接等不确定来源的情境。相较于 execl,execv 更适合自动化、循环、结构化传参。
3.4 代码示例
#include <unistd.h>
#include <stdio.h>
int main() {
printf("Before execv\n");
char *args[] = { (char *)"ls", (char *)"-lh", NULL };
execv("/usr/bin/ls", args);
// 如果 execv 成功,下面语句不会被执行
printf("After execv\n");
return 0;
}
编译:
g++ execv_example.cpp -o execv_example
./execv_example
输出结果:
Before execv
total 8.0K
-rwxr-xr-x 1 user user 8.2K May 7 10:20 execv_example
3.5 注意事项
- 参数数组最后必须以 NULL 结束,否则程序可能崩溃。
- 和所有 exec 函数一样,成功不会返回,失败返回 -1,可配合 perror() 使用:
四、execvp
4.1 基本概念
execvp 是 Linux/Unix 系统中 exec 系列函数的一员,它结合了参数数组传递(像 execv)和自动搜索 $PATH(像 execlp)两大优点。
4.2 函数原型
int execvp(const char *file, char *const argv[]);
参数说明:
参数 | 说明 |
---|---|
file | 要执行的程序名,可以是 "ls" 、"python" ,不需要写绝对路径 |
argv | 参数数组,必须以 NULL 结尾,argv[0] 通常是程序名 |
4.3 使用场景
当你想执行某个在 $PATH 中的命令,且参数来自于变量、用户输入、配置文件等来源,使用 execvp 是最灵活且安全的方式。
4.4 代码举例
#include <unistd.h>
#include <stdio.h>
int main() {
printf("Before execvp\n");
char *args[] = { (char *)"ls", (char *)"-l", (char *)"/home", NULL };
execvp("ls", args);
// 如果 execvp 成功,这里不会执行
perror("execvp failed");
printf("After execvp\n");
return 0;
}
编译代码:
g++ execvp_example.cpp -o execvp_example
./execvp_example
输出示例:
Before execvp
drwxr-xr-x 1 user user 4096 May 7 10:00 youruser
4.5 注意事项
- argv 数组必须以 NULL 结尾。
- 不需要提供绝对路径,程序会在 $PATH 环境变量下自动搜索。
- 适合构造命令行工具、脚本调用替换的情境,特别是命令和参数都不是写死的时候。
- 如果命令不存在或参数不合法,返回 -1,并设置 errno,建议使用:
五、execvpe
5.1 基本概念
execvpe 是 exec 系列函数中功能最完整的一个,它不仅支持通过参数数组传参(像 execv、execvp)自动搜索 $PATH(像 execvp),还增加了对环境变量的自定义控制,可以在执行新程序时指定一整套新的环境变量。
.
5.2 函数原型
int execvpe(const char *file, char *const argv[], char *const envp[]);
参数说明:
参数 | 说明 |
---|---|
file | 要执行的程序名,不要求是绝对路径,将自动在 $PATH 中查找 |
argv | 参数数组,必须以 NULL 结尾 |
envp | 环境变量数组(形如 "VAR=value" 的字符串数组),也必须以 NULL 结尾 |
5.3 使用场景
想执行某个命令(如 “python3”、“ls” 等)并传递参数,同时希望新程序运行在一个全新的环境中,不继承当前进程的 PATH、LD_LIBRARY_PATH 等环境变量。
5.4 代码示例
#include <unistd.h>
#include <stdio.h>
int main() {
char *argv[] = { (char *)"env", NULL };
// 指定新的环境变量,只有 PATH 和 MYVAR 被保留
char *envp[] = {
(char *)"PATH=/usr/bin:/bin",
(char *)"MYVAR=custom_value",
NULL
};
execvpe("env", argv, envp);
// 如果 execvpe 成功,以下不会执行
perror("execvpe failed");
return 1;
}
编译:
g++ execvpe_example.cpp -o execvpe_example
./execvpe_example
输出:
MYVAR=custom_value
PATH=/usr/bin:/bin
说明:新程序(env)运行时完全使用了我们传入的环境变量数组。
5.5 注意事项
- envp 必须是以 NULL 结尾的字符串数组,每一项格式为 “KEY=value”。
- 不同于其他 exec* 函数,execvpe 可以完全摆脱当前进程环境的约束。
- 如果执行失败(如命令找不到、无权限等),会返回 -1,设置 errno,用 perror 打印错误。
六、execve
6.1 基本概念
execve 是 Linux 系统中用于进程程序替换的 最原始、最基础 的函数。其他所有 exec 系列函数(比如 execl、execvp、execvpe 等)都是对它的封装。它直接调用内核提供的系统调用接口,真正完成“用新程序替换当前进程”的核心操作。
6.2 函数原型
int execve(const char *pathname, char *const argv[], char *const envp[]);
参数说明:
参数 | 说明 |
---|---|
pathname | 要执行的程序路径,必须是绝对路径或者相对路径,不支持 PATH 查找机制 |
argv | 参数列表,第一个参数通常是程序名,数组必须以 NULL 结尾 |
envp | 环境变量数组(KEY=value 的字符串),必须以 NULL 结尾 |
注意:execve 不会自动使用当前进程的环境变量,你必须手动传入。
6.3 示例代码
#include <unistd.h>
#include <stdio.h>
int main() {
char *argv[] = { (char *)"ls", (char *)"-l", NULL };
// 构建一个最小的环境变量数组
char *envp[] = {
(char *)"PATH=/usr/bin:/bin",
(char *)"LANG=C",
NULL
};
// 注意必须指定绝对路径
execve("/bin/ls", argv, envp);
// execve 成功后,这行不会执行
perror("execve failed");
return 1;
}
编译运行:
g++ execve_example.cpp -o execve_example
./execve_example
输出:
total 8
-rwxr-xr-x 1 user user 8320 May 7 10:00 execve_example
...
七、总结
exec 系列函数对比总结
特性/函数名 | execl | execlp | execv | execvp | execvpe | execve |
---|---|---|---|---|---|---|
函数原型 | int execl(const char *path, const char *arg0, ..., (char *)NULL); | int execlp(const char *file, const char *arg0, ..., (char *)NULL); | int execv(const char *path, char *const argv[]); | int execvp(const char *file, char *const argv[]); | int execvpe(const char *file, char *const argv[], char *const envp[]); | int execve(const char *pathname, char *const argv[], char *const envp[]); |
路径查找机制 | 需要提供 完整路径(不进行路径查找) | 通过 PATH 环境变量查找路径 | 需要提供 完整路径(不进行路径查找) | 通过 PATH 环境变量查找路径 | 通过 PATH 环境变量查找路径 | 需要提供 完整路径(不进行路径查找) |
环境变量支持 | 不传递环境变量(继承当前进程环境) | 不传递环境变量(继承当前进程环境) | 不传递环境变量(继承当前进程环境) | 不传递环境变量(继承当前进程环境) | 可以传递新的环境变量 | 需要手动传递新的环境变量 |
参数传递方式 | 可变参数列表,使用 ... 表示 | 可变参数列表,使用 ... 表示 | 使用 argv[] 数组(参数列表) | 使用 argv[] 数组(参数列表) | 使用 argv[] 和 envp[] 数组 | 使用 argv[] 和 envp[] 数组 |
参数结尾标志 | 以 NULL 结尾表示参数列表结束 | 以 NULL 结尾表示参数列表结束 | 以 NULL 结尾表示参数列表结束 | 以 NULL 结尾表示参数列表结束 | 以 NULL 结尾表示参数列表结束 | 以 NULL 结尾表示参数列表结束 |
返回值 | 成功时不返回;失败时返回 -1 | 成功时不返回;失败时返回 -1 | 成功时不返回;失败时返回 -1 | 成功时不返回;失败时返回 -1 | 成功时不返回;失败时返回 -1 | 成功时不返回;失败时返回 -1 |
常用场景 | 参数数量固定,且路径已知 | 需要自动查找路径(通过 PATH ) | 参数数量固定,路径已知 | 需要自动查找路径(通过 PATH ) | 需要自动查找路径且传递新的环境变量 | 完全自定义程序路径与环境 |
是否支持环境变量传递 | 不支持环境变量传递 | 不支持环境变量传递 | 不支持环境变量传递 | 不支持环境变量传递 | 支持环境变量传递 | 支持环境变量传递 |
路径处理 | 需要完整路径 | 通过 PATH 查找 | 需要完整路径 | 通过 PATH 查找 | 通过 PATH 查找 | 需要完整路径 |
应用场景 | 简单的程序替换(路径已知,少量参数) | 需要动态查找路径并执行程序 | 自定义程序路径、执行复杂参数 | 需要动态查找路径并执行程序 | 需要动态查找路径并传递新环境变量 | 底层调用,完全自定义程序路径和环境 |
![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/7c12c8c71e8b48b8a282eee702b69056.png