进程的程序替换,exec 函数族。

fork 创建子进程后执行的是和父进程相同的程序,如果需要他执行不同的代码分支,子进程旺旺需要调用一种 exec 函数以执行另一个程序。当进程调用一种 exec 函数时,该进程的用户空间代码的数据完全被新程序替换,从新程序的启动例程开始执行。调用 exec 并不创建新的进程,所以调用 exec 前后该进程的 id 并未改变。

当前进程的 .text 和 .data 替换为所需要加载的程序的 .text 和 .data,然后让进程从新的 .text 第一条指令开始执行,进程的 id 不变,换核子不换壳子。

exec 函数族的一些参数有些不同,底层实现原理一样。

1
2
3
4
5
6
7
8
9
SYNOPSIS
#include <unistd.h>
extern char **environ;
int execl(const char *path, const char *arg0, ... /*, (char *)0 */);
int execle(const char *path, const char *arg0, ... /*, (char *)0, char *const envp[] */);
int execlp(const char *file, const char *arg0, ... /*, (char *)0 */);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execvP(const char *file, const char *search_path, char *const argv[]);
  • l(list) : 表示参数采用列表
  • v(vector) : 参数用数组
  • p(path) : 有p自动搜索环境变量PATH
  • e(env) : 表示自己维护环境变量

事实上,只有 execve 是真正的系统调用,其他五个函数最终都调用 execve,所以 execve 在 man 手册第 2 节,其他函数在 man 手册的第 3 节。这些函数之间的关系如下图:

exec函数族,图片来源于网络

execl()

加载一个进程,通过 路径+程序名 来加载。

1
int execl(const char *path, const char *arg0, ... /*, (char *)0 */);

返回:成功:无返回;失败:-1。

参数:path:可执行程序的路径,如/bin/ls。后面跟要传给这个程序的参数。

有了这个函数就可以来加载一个自定义的程序了。

栗子:

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

int main(void) {
pid_t ret = fork();
if (ret == -1) {
perror("fork error");
exit(-1);
} else if (ret == 0) {
// 子进程
execl("/bin/ls", "ls", "-a", NULL);
} else {
// 父进程
sleep(1);
printf("father process\n");
}
return 0;
}

execl_ls.c

execlp()

加载一个进程,借助 PATH 环境变量。

1
int execlp(const char *file, const char *arg0, ... /*, (char *)0 */);

返回:成功:没有返回;失败:-1。

参数:file:要加载的程序名。该函数需要配合 PATH 环境变量来使用,当 PATH 中所有目录搜索后没有参数 file 则返回出错。后面跟传给要执行程序的参数。

该函数通常用来调用系统程序。如:ls、date、cp、cat 等。

栗子:

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

int main(void) {
pid_t ret = fork();
if (ret == -1) {
perror("fork error");
} else if (ret == 0) {
// 子进程
execlp("ls", "ls", "-al", NULL); // 注意第二个参数 ls 对应到main函数的参数就是argv[0],传给ls其实他并没有使用这个参数
} else {
// 父进程
sleep(1);
printf("father process\n");
}
return 0;
}

execlp_ls.c

execle()

最后一个参数是一个数组,自己的环境变量数组,这个数组必须以 NULL 结尾。

1
int execle(const char *path, const char *arg0, ... /*, (char *)0, char *const envp[] */);

execv()

1
int execv(const char *path, char *const argv[]);

栗子:

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

int main(void) {
pid_t ret = fork();
if (ret == -1) {
perror("fork error");
exit(-1);
} else if (ret == 0) {
// 子进程
char* argv[] = {"ls", "-a", NULL};
execv("/bin/ls", argv);
} else {
// 父进程
sleep(1);
printf("father process\n");
}
return 0;
}

execv_ls.c