「Linux 系统编程」exec 函数族及使用示例

1 exec 函数族

在 Linux 系统编程中,fork() 函数用于创建一个新的进程,这个新进程是当前进程的副本。如果我们想要新进程执行一个全新的、不同的程序,这个时候就需要用到 exec 函数族。

exec 是 Linux 中一组功能强大且常用的函数,负责将当前进程的镜像替换为一个新的程序镜像 —— 进程的 PID 保持不变,但运行的程序被彻底替换

1.1 exec 函数族概览

exec 函数族包含了多个函数,它们的核心功能相同(加载新程序),在参数传递方式上略有差异。这些函数都定义在 <unistd.h> 中。

函数原型 参数特点 路径搜索 环境变量
int execl(const char *path, const char *arg, ...); 列表 (List) (需全路径) 继承父进程
int execlp(const char *file, const char *arg, ...); 列表 (List) (PATH) 继承父进程
int execle(const char *path, const char *arg, ..., char *const envp[]); 列表 (List) (需全路径) 自定义
int execv(const char *path, char *const argv[]); 向量 (Vector/数组) (需全路径) 继承父进程
int execvp(const char *file, char *const argv[]); 向量 (Vector/数组) (PATH) 继承父进程
int execvpe(const char *file, char *const argv[], char *const envp[]); 向量 (Vector/数组) (PATH) 自定义

注意execvpe 是一个 GNU 扩展,并非所有 UNIX 系统都支持(但主流 Linux 发行版都支持)。

区分 exec 函数族每个函数:

  • l (list):以列表的形式设置命令行参数
  • p (path):搜索 file 时使用 path 变量
  • v (vector):以数组的形式设置命令行
  • e (environment):使用环境变量数组,不适用进程原有的环境变量,设置新加载程序运行的环境变量

1.2 exec 函数参数

  • const char *file / const char *path:执行的程序。如果是 file 需要借助 PATH 环境变量寻找待执行程序;如果是 path 必须是完整的路径。

  • const char *arg, ...:对应的分别是命令行参数的 argv[0], argv[1], argv[2], ...,并且最后需要添加一个 NULL 表示参数结束。

  • char *const argv[]:与 const char *arg, ... 同理,参数数组的最后必须以 NULL 结尾。


1.3 exec 函数返回值

  • exec 函数一旦调用成功,即执行新的程序,不返回
  • exec 只有失败才返回,返回 -1。因此通常直接在 exec 函数调用后直接调用 perror() 等错误判断。


2 execl、execlp (参数列表形式)

2.1 函数原型

1
2
int execl(const char *path, const char *arg0, const char *arg1, ..., (char *) NULL);
int execlp(const char *file, const char *arg0, const char *arg1, ..., (char *) NULL);
  • path/file: 要执行的程序的路径。

    • execl 需要 绝对路径相对路径(如 /bin/ls./my_program)。
    • execlp 只需要 程序名,系统会在 $PATH 环境变量指定的目录中搜索该程序。
  • arg0, arg1, ...: 命令行参数列表。arg0 是程序名本身(argv[0])。

  • ...: 参数需要以 (char *) NULL 结尾,表示参数列表结束。


2.2 代码示例

  • 使用 execl 执行 ls -lh

    1
    2
    3
    4
    5
    6
    7
    8
    9
    #include <unistd.h>

    int main(){
    execl("/bin/ls", "ls", "-l", "-h", (char*)NULL);

    // execl 失败才会执行后续代码
    perror("execl error");
    return 1;
    }


  • 使用 execlp 执行 ps aux

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    #include <unistd.h>

    int main(){
    // 系统会在 PATH 中找到 "ps" 命令
    execlp("ps", "ps", "aux", (char*)NULL);

    // execlp 失败才会执行后续代码
    perror("execlp failed");
    return 1;
    }



3 execv、execvp (参数数组形式)

3.1 函数原型

1
2
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
  • path/file: 同 execl/execlp

  • argv[]: 一个字符串指针数组,包含了所有命令行参数。该数组的最后一个元素必须是 NULL


3.2 代码示例

使用 execvp 执行 ls -l /home/[username]

1
2
3
4
5
6
7
8
9
10
11
12
#include <unistd.h>

int main(){
// 参数数组
char* args[] = {"ls", "-l", "/home/marisa", NULL};

// 使用 execvp 执行
execvp("ls", args);

perror("execvp failed");
return 1;
}



4 execle、execvpe (自定义环境变量)

4.1 函数原型

1
2
int execle(const char *path, const char *arg0, ..., (char *) NULL, char *const envp[]);
int execvpe(const char *file, char *const argv[], char *const envp[]);
  • envp[]: 一个字符串指针数组,代表新程序的环境变量。数组以 NULL 结尾。每个字符串的格式通常是 “KEY=VALUE”。

envp[] 为新的程序进程提供一个 完全自定义的环境,而不是继承父进程的环境。


4.2 代码示例

使用 execle 执行一个程序并传递自定义环境

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdio.h>
#include <unistd.h>

int main() {
char* new_envp[] = {
"MY_VAR=Hello_World",
"PATH=/usr/bin:/bin",
NULL // 环境变量数组也必须以 NULL 结尾
};

// print_env 打印环境变量
execle("./print_env", "print_env", NULL, new_envp);

perror("execle failed");
return 1;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// print_env.c
#include <stdio.h>

// 外部变量 environ,它是由C库提供的指向环境变量数组的指针
extern char **environ;

int main(int argc, char *argv[]) {
printf("Printing all environment variables:\n");
printf("===================================\n");

// 遍历环境变量数组,直到遇到NULL指针
for (char **env = environ; *env != NULL; env++) {
printf("%s\n", *env);
}

// 也可以使用 main 的第三个参数(如果使用 execle 等方式传递了环境变量)
// 但更通用的方法是使用 environ 变量

return 0;
}

print_env 程序只是打印它的所有环境变量,只会看到 MY_VARPATH,而看不到父进程原有的其他环境变量。



5 fork() 结合 exec 调用新程序

5.1 exec 核心特性

  • exec 调用成功,当前进程的代码、数据、堆、栈等所有段都被新程序彻底覆盖exec 之后的代码永远不会被执行(除非调用失败)。

  • 进程的 PID、PPID(父进程ID)、文件描述符、信号处理方式等属性会被保留。新程序可以继承父进程打开的文件、网络连接等资源。

fork() 结合 exec 代码示例

exec 最常见的用法是与 fork() 结合:

  • 父进程调用 fork() 创建子进程。
  • 子进程调用 exec() 来执行另一个程序。
  • 父进程通常调用 wait() 来等待子进程结束。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <unistd.h>
#include <sys/wait.h>
#include <stdio.h>

int main() {
pid_t pid = fork();

if (pid == -1) {
perror("fork failed");
return 1;
} else if (pid == 0) {
// 子进程
printf("I'm the child process (PID: %d).\n", getpid());
execlp("ls", "ls", "-l", NULL);
// 如果 exec 失败
perror("execlp failed");
return 1; // 子进程退出
} else {
// 父进程
printf("I'm the parent process (PID: %d), my child is %d.\n", getpid(), pid);
wait(NULL); // 等待子进程结束
printf("Child process finished.\n");
}

return 0;
}


「Linux 系统编程」exec 函数族及使用示例
https://marisamagic.github.io/2025/09/02/20250902/
作者
MarisaMagic
发布于
2025年9月2日
许可协议