程序替换
一、作用/目的
fork() 之后,父子各自执行父进程代码的一部分如果子进程就想执行一个全新的程序呢?进程的程序替换来完成这个功能!程序替换是通过特定的接口,加载磁盘上的一个全新的程序(代码和数据),加载到调用进程的地址空间中!
二、替换原理
用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种exec函数以执行另一个程序。当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。调用 exec 并不创建新进程,所以掉用 exec 前后该进程的id没有改变。
本质:加载(在当前进程当中加载新的代码和数据)
三、替换函数
有七种以exec开头的函数,统称exec函数: 
cpp
#include <unistd.h>
int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg, ...,char *const envp[]);
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[]);
// 上面六个函数是库函数
// 下面这个函数是系统调用,envp没有传,默认是environ
int execve(const char *filename, char *const argv[], char *const envp[]);

这些函数如果调用成功则加载新的程序从启动代码开始执行,不再返回。
如果调用出错则返回 -1(路径或者选项写错了)
所以 exec 函数只有出错的返回值,而没有成功的返回值。

命名理解
- l(list):表示参数采用列表
- v(vector):参数用数组
- p(path):有p自动搜索环境变量PATH
- e(env):表示自己维护环境变量

1.execl
cpp
int execl(const char *path, const char *arg, ...);
- path:你要执行的程序在哪里(路径/文件名)
- ...:可变参数列表,(要执行的命令怎么写,这里就怎么写),必须以NULL结尾。
- 部分参数可以省略,但是不建议。
直接看代码和现象:
cpp
1 #include <stdio.h>
2 #include <unistd.h>
3
4
5 int main()
6 {
7 printf("我变成了一个进程:%d\n", getpid());
8
9
10 execl("/usr/bin/ls", "ls", "-a", "-l", NULL);//程序替换函数
11
12 printf("我的代码运行中...\n");
13 printf("我的代码运行中...\n");
14 printf("我的代码运行中...\n");
15 printf("我的代码运行中...\n");
16 printf("我的代码运行中...\n");
17 printf("我的代码运行中...\n");
18 printf("我的代码运行中...\n");
19 printf("我的代码运行中...\n");
20
21 return 0;
22 }
现象:
bash
[zhangsan@hcss-ecs-f571 exec]$ ./myexec
我变成了一个进程:22058
total 20
-rw-rw-r-- 1 zhangsan zhangsan 79 Dec 21 22:33 Makefile
-rwxrwxr-x 1 zhangsan zhangsan 8512 Dec 21 22:33 myexec
-rw-rw-r-- 1 zhangsan zhangsan 537 Dec 21 22:32 myexec.c
可以看到,execl函数直接执行ls函数,后面的打印程序的代码被覆盖了。
注意:参数列表要以NULL结尾。
下面案例同上,虽然exec在错误的时候有返回值,但是下面代码这种方式也可以直接判断,execl执行错误,就会执行exit(1),说明execl执行错误了。
cpp
1 #include <stdio.h>
2 #include <unistd.h>
3 #include <stdlib.h>
4 #include <sys/wait.h>
5 #include <sys/types.h>
6
7
8 int main()
9 {
10 printf("我变成了一个进程:%d\n", getpid());
11
12 pid_t id = fork();
13 if(id == 0)
14 {
15 // execl("/usr/bin/ls", "-a", "-l", NULL);//程序替换函数
16 execl("/usr/bin/top", "top", "-d", "1", "-n", "3", NULL);
17 exit(1);
18 }
19 int status = 0;
20 pid_t rid = waitpid(id, &status, 0);
21 if(rid > 0)
22 {
23 printf("wait sucess, exit code:%d\n", WEXITSTATUS(status));
24 }
25
26
27 return 0;
28 }
2.execlp
cpp
int execlp(const char *file, const char *arg, ...);
- p(path):有p自动搜索环境变量PATH,子进程可以继承父进程的PATH。
- file:不用带路径了,只需要程序名就可以
- ...:可变参数列表,(要执行的命令怎么写,这里就怎么写),必须以NULL结尾。
- 部分参数可以省略,但是不建议。
代码:
cpp
1 #include <stdio.h>
2 #include <unistd.h>
3 #include <stdlib.h>
4 #include <sys/wait.h>
5 #include <sys/types.h>
6
7
8 int main()
9 {
10 printf("我变成了一个进程:%d\n", getpid());
11
12 pid_t id = fork();
13 if(id == 0)
14 {
15 sleep(2);
16 printf("下面代码都是子进程在执行");
17 execlp("ls", "ls", "-a", "-l", NULL);//程序替换函数
18 // execl("/usr/bin/ls", "-a", "-l", NULL);//程序替换函数
19 // execl("/usr/bin/top", "top", "-d", "1", "-n", "3", NULL);
20 exit(1);
21 }
22 int status = 0;
23 pid_t rid = waitpid(id, &status, 0);
24 if(rid > 0)
25 {
26 printf("wait sucess, exit code:%d\n", WEXITSTATUS(status));
27 }
28
29
30 return 0;
31 }
运行结果:
bash
[zhangsan@hcss-ecs-f571 exec]$ ./myexec
我变成了一个进程:3174
下面代码都是子进程在执行
total 28
drwxrwxr-x 2 zhangsan zhangsan 4096 Dec 22 00:17 .
drwxrwxr-x 3 zhangsan zhangsan 4096 Dec 21 22:26 ..
-rw-rw-r-- 1 zhangsan zhangsan 79 Dec 21 22:33 Makefile
-rwxrwxr-x 1 zhangsan zhangsan 8720 Dec 22 00:17 myexec
-rw-rw-r-- 1 zhangsan zhangsan 707 Dec 22 00:17 myexec.c
wait sucess, exit code:0
[zhangsan@hcss-ecs-f571 exec]$
3.execv
cpp
int execv(const char *path, char *const argv[]);
- v(vector):参数用数组
- path:你要执行的程序在哪里(路径/文件名)
代码:
cpp
1 #include <stdio.h>
2 #include <unistd.h>
3 #include <stdlib.h>
4 #include <sys/wait.h>
5 #include <sys/types.h>
6
7
8 int main()
9 {
10 printf("我变成了一个进程:%d\n", getpid());
11
12 pid_t id = fork();
13 if(id == 0)
14 {
15 sleep(2);
16 printf("下面代码都是子进程在执行\n");
17 char* const argv[]={
18 (char*) "ls",
19 (char*) "-a",
20 (char*) "-l",
21 NULL
22 };
23 execv("/usr/bin/ls", argv);//程序替换函数
24 // execlp("ls", "ls", "-a", "-l", NULL);//程序替换函数
25 // execl("/usr/bin/ls", "-a", "-l", NULL);//程序替换函数
26 // execl("/usr/bin/top", "top", "-d", "1", "-n", "3", NULL);
27 exit(1);
28 }
29 int status = 0;
30 pid_t rid = waitpid(id, &status, 0);
31 if(rid > 0)
32 {
33 printf("wait sucess, exit code:%d\n", WEXITSTATUS(status));
34 }
35
36
37 return 0;
38 }
这里的ls也是一个程序,也是用c写的,所以argv数组是作为main(int argc, char* argv) 函数的参数传进去的。
4.execvp
cpp
int execvp(const char *file, char *const argv[]);
- v(vector):参数用数组
- p(path):有p自动搜索环境变量PATH,子进程可以继承父进程的PATH。
cpp
1 #include <stdio.h>
2 #include <unistd.h>
3 #include <stdlib.h>
4 #include <sys/wait.h>
5 #include <sys/types.h>
6
7
8 int main()
9 {
10 printf("我变成了一个进程:%d\n", getpid());
11
12 pid_t id = fork();
13 if(id == 0)
14 {
15 sleep(2);
16 printf("下面代码都是子进程在执行\n");
17 char* const argv[]={
18 (char*) "ls",
19 (char*) "-a",
20 (char*) "-l",
21 NULL
22 };
23
24 execvp(argv[0], argv);//程序替换函数
25 // execv("/usr/bin/ls", argv);//程序替换函数
26 // execlp("ls", "ls", "-a", "-l", NULL);//程序替换函数
27 // execl("/usr/bin/ls", "-a", "-l", NULL);//程序替换函数
28 // execl("/usr/bin/top", "top", "-d", "1", "-n", "3", NULL);
29 exit(1);
30 }
31 int status = 0;
32 pid_t rid = waitpid(id, &status, 0);
33 if(rid > 0)
34 {
35 printf("wait sucess, exit code:%d\n", WEXITSTATUS(status));
36 }
37
38
39 return 0;
40 }
**问题:**能替换系统命令,能不能替换自己写的程序???
下面代码中的othercmd是自己用C++实现的程序。
cpp
1 #include <stdio.h>
2 #include <unistd.h>
3 #include <stdlib.h>
4 #include <sys/wait.h>
5 #include <sys/types.h>
6
7
8 int main()
9 {
10 printf("我变成了一个进程:%d\n", getpid());
11
12 pid_t id = fork();
13 if(id == 0)
14 {
15 sleep(2);
16 printf("下面代码都是子进程在执行\n");
17 execl("./othercmd","./othercmd", NULL);//程序替换函数
18 // char* const argv[]={
19 // (char*) "ls",
20 // (char*) "-a",
21 // (char*) "-l",
22 // (char*) "--color=auto",
23 // NULL
24 // };
25
26 // execvp(argv[0], argv);//程序替换函数
27 // execv("/usr/bin/ls", argv);//程序替换函数
28 // execlp("ls", "ls", "-a", "-l", NULL);//程序替换函数
29 // execl("/usr/bin/ls", "-a", "-l", NULL);//程序替换函数
30 // execl("/usr/bin/top", "top", "-d", "1", "-n", "3", NULL);
31 exit(1);
32 }
33 int status = 0;
34 pid_t rid = waitpid(id, &status, 0);
35 if(rid > 0)
36 {
37 printf("wait sucess, exit code:%d\n", WEXITSTATUS(status));
38 }
39
40
41 return 0;
42 }
运行结果:
cpp
[zhangsan@hcss-ecs-f571 exec]$ ./myexec
我变成了一个进程:23083
下面代码都是子进程在执行
我是自己的c++程序
wait sucess, exit code:0
同理,也可以直接替换其他语言
创建了一个test.sh的文件,里面是一下bash是命令
bash
1 #!/bin/bash
2 # echo "hello word"
3 cnt=1
4 while [ $cnt -le 10 ]
5 do
6 echo "hello $cnt"
7 let cnt++
8 done
直接用execl来程序替换
cpp
1 #include <stdio.h>
2 #include <unistd.h>
3 #include <stdlib.h>
4 #include <sys/wait.h>
5 #include <sys/types.h>
6
7
8 int main()
9 {
10 printf("我变成了一个进程:%d\n", getpid());
11
12 pid_t id = fork();
13 if(id == 0)
14 {
15 sleep(2);
16 printf("下面代码都是子进程在执行\n");
17 execl("/usr/bin/bash","bash","./cmd/test.sh", NULL);//程序替换函数
18 // char* const argv[]={
19 // (char*) "ls",
20 // (char*) "-a",
21 // (char*) "-l",
22 // (char*) "--color=auto",
23 // NULL
24 // };
25
26 // execvp(argv[0], argv);//程序替换函数
27 // execv("/usr/bin/ls", argv);//程序替换函数
28 // execlp("ls", "ls", "-a", "-l", NULL);//程序替换函数
29 // execl("/usr/bin/ls", "-a", "-l", NULL);//程序替换函数
30 // execl("/usr/bin/top", "top", "-d", "1", "-n", "3", NULL);
31 exit(1);
32 }
33 int status = 0;
34 pid_t rid = waitpid(id, &status, 0);
35 if(rid > 0)
36 {
37 printf("wait sucess, exit code:%d\n", WEXITSTATUS(status));
38 }
39
40
41 return 0;
42 }
运行结果:
bash
[zhangsan@hcss-ecs-f571 exec]$ ./myexec
我变成了一个进程:26397
下面代码都是子进程在执行
hello 1
hello 2
hello 3
hello 4
hello 5
hello 6
hello 7
hello 8
hello 9
hello 10
wait sucess, exit code:0
5.execvpe
cpp
int execvpe(const char *file, char *const argv[], char *const envp[]);
- v(vector):参数用数组
- p(path):有p自动搜索环境变量PATH
- e(env):表示自己维护环境变量,可以传全新的环境变量
这里先自己实现一个c++程序
cpp
1 #include <iostream>
2 #include <stdio.h>
3
4 int main(int argc, char *argv[], char *env[])
5 {
6 for(int i = 0; i < argc;i++)
7 {
8 printf("argv[%d]: %s\n", i, argv[i]);
9 }
10 std::cout << "\r\n";
11 for(int j = 0;env[j]; j++)
12 {
13 printf("env[%d]:%s\n",j, env[j]);
14 }
15 std::cout << "我是自己的c++程序"<< std::endl;
16 return 0;
17 }
这个程序可以打印出自己的参数和环境变量表
在另外一个函数中,调用上面的C++程序,并且修改环境变量
cpp
1 #include <stdio.h>
2 #include <unistd.h>
3 #include <stdlib.h>
4 #include <sys/wait.h>
5 #include <sys/types.h>
6
7
8 int main()
9 {
10 printf("我变成了一个进程:%d\n", getpid());
11
12 pid_t id = fork();
13 if(id == 0)
14 {
15 sleep(2);
16 printf("下面代码都是子进程在执行\n");
17 char* myargv[] = {
18 (char*) "othercmd",
19 (char*) "-a",
20 (char*) "-b",
21 NULL
22 };
23 char* myenv[] = {
24 (char*) "path=/home/zhangsan/learn_-linux/learn_12_19/exec/cmd"
25 ,NULL
26 };
27 extern char** environ;
28 putenv((char*)"haha=aaaaaaaaaaaaaaaaaa");
29 execvpe("./cmd/othercmd", myargv, environ);//父亲的环境变量 + 自己的环境变量
30 execvpe("./cmd/othercmd", myargv, myenv);//覆盖式的使用全新的环境变量表
31 execvpe("./cmd/othercmd", myargv, environ);// 使用父进程的环境变量表
32 exit(3);
33 // execl("/usr/bin/bash","bash","./cmd/test.sh", NULL);//程序替换函数
34 // char* const argv[]={
35 // (char*) "ls",
36 // (char*) "-a",
37 // (char*) "-l",
38 // (char*) "--color=auto",
39 // NULL
40 // };
41
42 // execvp(argv[0], argv);//程序替换函数
43 // execv("/usr/bin/ls", argv);//程序替换函数
44 // execlp("ls", "ls", "-a", "-l", NULL);//程序替换函数
45 // execl("/usr/bin/ls", "-a", "-l", NULL);//程序替换函数
46 // execl("/usr/bin/top", "top", "-d", "1", "-n", "3", NULL);
47 exit(1);
48 }
49 int status = 0;
50 pid_t rid = waitpid(id, &status, 0);
51 if(rid > 0)
52 {
53 printf("wait sucess, exit code:%d\n", WEXITSTATUS(status));
54 }
55
56
57 return 0;
58 }
运行结果:会发现成功设置了自己的环境变量

结论:命令行参数和环境变量表,都是父进程通过exec*传给你的。