目录
在 Linux 中,子进程在创建后可以通过 exec
系列系统调用执行一个全新的程序。这种情况下,子进程会替换原有的代码和数据段,运行一个新的可执行程序,但它的进程 ID(PID)保持不变。exec
系列调用包括多个变体,常见的有 execl()
、execv()
、execle()
、execve()
等,它们的主要区别在于参数传递方式不同。
子进程执行新程序的流程如下:
- 创建子进程 :使用
fork()
创建子进程。 - 调用
exec
:在子进程中调用exec
执行新程序。 - 替换子进程的内存映像 :
exec
会替换子进程的整个内存空间,包括代码段、数据段、堆栈等,只保留进程的 PID 和一些特定属性。 - 父进程继续执行 :父进程保持不变,继续执行它的代码,直到调用
wait()
等待子进程结束。
exec
系列函数通过不同的方式传递参数和环境变量,能够实现灵活的程序替换。具体函数如下:
execve()
是最基础的调用,允许自定义环境变量和参数。execl()
和execv()
提供了简化接口,execl()
使用可变参数,execv()
使用参数数组。execlp()
和execvp()
可以根据PATH
环境变量搜索程序。execle()
和execvpe()
提供自定义环境变量的支持。
1、execve()
execve()
是最基础的 exec
函数,所有其他 exec
系列函数都是基于它的。它直接接受路径名、参数数组和环境变量数组。
函数原型如下:
cpp
int execve(const char *filename, char *const argv[], char *const envp[]);
参数如下:
filename
:要执行的文件的路径(绝对或相对路径)。argv[]
:参数列表(传递给程序的命令行参数)。第一个参数通常是程序本身的名称。envp[]
:环境变量列表。
使用 execve()
执行 /bin/ls
,传递了参数 -l
和环境变量 PATH
。
cpp
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void) {
char *argv[] = {"ls", "-l", NULL}; // 参数列表
char *envp[] = {"PATH=/bin", NULL}; // 环境变量
printf("执行 ls 程序\n");
if (execve("/bin/ls", argv, envp) == -1) {
perror("execve error");
}
return 0;
}
2、execl()
execl()
是 execve()
的简化版本,参数以可变长度的方式传递(列表形式)。最后一个参数必须是 (char *) NULL
。
函数原型如下:
cpp
int execl(const char *path, const char *arg, ... /* (char *) NULL */);
参数如下:
path
:可执行文件的路径。arg
:程序名称后接任意数量的参数,最后以NULL
结束。
以下例子调用 execl()
,通过可变参数传递给 ls
程序。
cpp
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void) {
printf("使用 execl 执行 ls 程序\n");
execl("/bin/ls", "ls", "-l", (char *) NULL); // 传递可变长度参数列表
// 如果 exec 调用失败,返回 -1
perror("execl error");
return 0;
}
3、execlp()
execlp()
和 execl()
类似,但它不需要提供文件的完整路径。它会在 PATH
环境变量指定的目录中搜索可执行文件。
函数原型如下:
cpp
int execlp(const char *file, const char *arg, ... /* (char *) NULL */);
参数如下:
file
:要执行的程序名(无需完整路径)。arg
:程序名称及其他参数,最后以NULL
结束。
以下例中,execlp()
会根据 PATH
环境变量查找 ls
程序的路径。
cpp
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void) {
printf("使用 execlp 执行 ls 程序\n");
execlp("ls", "ls", "-l", (char *) NULL); // 使用环境变量中的路径搜索 ls 程序
perror("execlp error");
return 0;
}
4、execle()
execle()
类似于 execl()
,但允许传递环境变量数组。
函数原型如下:
cpp
int execle(const char *path, const char *arg, ... /*, (char *) NULL, char * const envp[] */);
参数如下:
path
:可执行文件路径。arg
:程序名称及其他参数,最后以NULL
结束。envp[]
:环境变量数组。
以下例子中,execle()
将自定义的环境变量传递给 ls
程序。
cpp
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void) {
char *envp[] = {"PATH=/bin", NULL}; // 设置环境变量
printf("使用 execle 执行 ls 程序\n");
execle("/bin/ls", "ls", "-l", (char *) NULL, envp); // 传递环境变量
perror("execle error");
return 0;
}
5、execv()
execv()
是 execve()
的简化版本,不需要传递环境变量,只需要路径和参数数组。
函数原型如下:
cpp
int execv(const char *path, char *const argv[]);
参数如下:
path
:可执行文件的路径。argv[]
:参数数组。
在该例中,execv()
使用参数数组执行 ls
。
cpp
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void) {
char *argv[] = {"ls", "-l", NULL}; // 参数数组
printf("使用 execv 执行 ls 程序\n");
execv("/bin/ls", argv); // 传递参数数组
perror("execv error");
return 0;
}
6、execvp()
execvp()
和 execv()
类似,但它会根据 PATH
环境变量查找可执行文件。
函数原型如下:
cpp
int execvp(const char *file, char *const argv[]);
参数如下:
file
:可执行文件的名称。argv[]
:参数数组。
execvp()
不要求完整路径,会自动在 PATH
中查找 ls
。
cpp
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void) {
char *argv[] = {"ls", "-l", NULL}; // 参数数组
printf("使用 execvp 执行 ls 程序\n");
execvp("ls", argv); // 根据 PATH 搜索并执行程序
perror("execvp error");
return 0;
}
7、execvpe()
execvpe()
是 execvp()
的扩展,允许传递自定义的环境变量。
函数原型如下:
cpp
int execvpe(const char *file, char *const argv[], char *const envp[]);
参数如下:
file
:要执行的程序名称。argv[]
:参数数组。envp[]
:环境变量数组。
在以下示例中,execvpe()
使用自定义环境变量执行程序。
cpp
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void) {
char *argv[] = {"ls", "-l", NULL}; // 参数数组
char *envp[] = {"PATH=/bin", NULL}; // 环境变量
printf("使用 execvpe 执行 ls 程序\n");
execvpe("ls", argv, envp); // 使用 PATH 环境变量执行程序
perror("execvpe error");
return 0;
}
子进程执行新程序时,可以通过 exec
系列系统调用替换子进程的内存空间,执行新的二进制程序。exec
系列调用的不同变体提供了灵活的参数传递方式,适应不同场景需求。通过合理使用 fork()
和 exec()
,可以实现高效的多进程编程,确保资源的有效利用和进程的灵活控制。