1.替换原理
用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种exec函数以执行另一个程序。当进程调用一种exec函数时,该进程的用户空间代码和数据 完全被新程序替换,从新程序的启动例程开始执行。调用exec并不创建新进程 ,所以调用exec前后该进程的id并未改变。
2.七大替换函数
进程程序替换函数共有七个,其中六个都是在调用函数6,因此函数6 execve 才是真正的系统级接口 

这些函数都属于 exec 替换家族,所以它们的返回值都一样。
注意: 这七个函数只有在程序替换失败后才会有返回值,返回 -1,程序替换成功后不返回。
程序都已经替换成功,后续代码也都将被替换,所以成功后的返回值也就没意义了
2.1 execl函数
函数原型:
cpp
#include <unistd.h>
int execl(const char* path, const char* arg, ...);
1.参数1:准备替换的可执行程序的路径。
2.参数2:准备替换的可执行程序的名字,像系统指令"ls"。
3.参数3:可变参数列表,如"-a","-l",最后一个需要设为NULL来结束


cpp
1 #include<stdio.h>
2 #include<unistd.h>
3 #include<stdlib.h>
4 int main()
5 {
6 printf("before I am a process,pid : %d,ppid : %d\n",getpid(),getppid());
7 execl("/usr/bin/ls","ls","-l",NULL);
8
9 printf("after I am a process,pid : %d,ppid : %d\n",getpid(),getppid());
10 }
运行结果:

因为调用execl函数把当前进程替换掉了,所以回去调用系统指令"ls -l",第9行的代码也不会被执行到。
2.2 execv函数
cpp
#include <unistd.h>
int execv(const char* path, char* const argv[]);
1.参数1:准备替换的可执行程序的路径。
2.参数2:以待替换程序名加后续要执行的指令构成的一张指针数组表。

创建指针数组表时,最后还是要加上NULL表示结束。
cpp
1 #include<stdio.h>
2 #include<unistd.h>
3 #include<stdlib.h>
4 #include <sys/types.h>
5 #include <sys/wait.h>
16 int main()
17 {
18 pid_t id=fork();
19 if(id==0)
20 {
21 char*const a[]=
22 {
23 "ls",
24 "-a",
25 "-l",
26 NULL
27 };
28 execv("/usr/bin/ls",a);
29 printf("process replace fail");
30 exit(-1);
31 }
32 pid_t n=wait(NULL);
33 }
与 execl 函数不同,execv 是以表的形式进行参数传递的。

2.3 execlp函数
可以不用写替换的可执行程序的路径,会去环境变量里面去查找路径。
cpp
#include <unistd.h>
int execlp(const char* file, const char* arg, ...);
1.参数1:准备替换的可执行程序的路径,需要在环境变量里面。
2.参数2:可参数列表,命令的选项。
cpp
37 int main()
38 {
39 pid_t id=fork();
40 if(id==0)
41 {
42 execlp("ls","ls","-a","-l",NULL);
43 printf("process replace fail");
44 exit(-1);
45 }
46 pid_t n=wait(NULL);
47 }
运行结果:

使用execlp时,需要保证准备替换的程序路径需要在环境变量中可以被找到。
2.4 execvp函数
cpp
#include <unistd.h>
int execvp(const char* file, char* const argv[]);
1.参数1:准备替换的可执行程序的路径,需要在环境变量里面。
2.参数2:代替换程序名及其指令选项构成的指针数组,最后要以NULL结尾。
cpp
51 int main()
52 {
53 pid_t id=fork();
54 if(id==0)
55 {
56 char*const argv[]=
57 {
58 "ls",
59 "-a",
60 "-l",
61 NULL
62 };
63 execvp("ls",argv);
64 printf("process replace fail");
65 exit(-1);
66 }
67 pid_t n=wait(NULL);
68 }
运行结果:

2.5 execle函数
如果想要使用自己设置的环境变量可以使用带"e"的exec系列接口。
cpp
#include <unistd.h>
int execle(const char* path, const char* arg, ..., char* const envp[]);
1.参数1:准备替换的可执行程序的路径。
2.参数2:可参数列表,命令的选项。
3.参数3:环境变量参数
1.父进程设置环境变量后 fork 子进程,子进程通过 execle 替换为自定义程序并传递环境变量。
cpp
1 #include<stdio.h>
2 #include<unistd.h>
3 #include<sys/types.h>
4 #include<sys/wait.h>
5 #include<stdlib.h>
6 int main()
7 {
8
9 putenv("wjp=666");
10 pid_t id=fork();
11 if(id==0)
12 {
13 sleep(2);
14 extern char**environ;
15 // char*const myarg[]={
16 // "test",
17 // "-a",
18 // NULL};
19 // execlp("pwd","pwd",NULL);
20 execle("./test","test","-a",NULL,environ);
21 }
22 printf("I am father,pid:%d,ppid:%d\n",getpid(),getppid());
23 pid_t rid=wait(NULL);
24 return 0;
25 }
cpp
69 #include<stdio.h>
70 int main(int argc,char*argv[],char*env[])
71 {
72 printf("running begin\n");
73 for(int i=0;argv[i];i++)
74 {
75 printf("%d : %s\n",i,argv[i]);
76 }
77 printf("-----------------------------------------");
78 for(int i=0;env[i];i++)
79 {
80 printf("%d : %s\n",i,env[i]);
81 }
82 return 0;
83 }
通过进程1创建子进程,把进程1的子进程替换为进程2。

2.替换成自己的环境变量
cpp
1 #include<stdio.h>
2 #include<unistd.h>
3 #include<sys/types.h>
4 #include<sys/wait.h>
5 #include<stdlib.h>
6 int main()
7 {
8
9 // putenv("wjp=666");
10 pid_t id=fork();
11 if(id==0)
12 {
13 sleep(2);
14 char*const myenv[]=
15 {
16 "myval=100",
17 NULL
18 };
19 // extern char**environ;
20 // char*const myarg[]={
21 // "test",
22 // "-a",
23 // NULL};
24 execle("./test","test","-a",NULL,myenv);
25 }
26 printf("I am father,pid:%d,ppid:%d\n",getpid(),getppid());
27 pid_t rid=wait(NULL);
28 return 0;
29 }
cpp
69 #include<stdio.h>
70 int main(int argc,char*argv[],char*env[])
71 {
72 printf("running begin\n");
73 for(int i=0;argv[i];i++)
74 {
75 printf("%d : %s\n",i,argv[i]);
76 }
77 printf("-----------------------------------------");
78 for(int i=0;env[i];i++)
79 {
80 printf("%d : %s\n",i,env[i]);
81 }
82 return 0;
83 }
运行结果:

替换的进程的环境变量被覆盖成了传递过去的环境变量。

2.6 execve函数
execve才是系统调用,上面的5个函数都是封装了execve。
cpp
#include <unistd.h>
int execve(const char* filename, char* const argv[], char* const envp[]);
1.参数1:代替换的程序路径
2.参数2:代替换的程序名及其命令选项构成的指针数组。
3.参数3:传递给替换程序的环境变量表。
环境变量:
execl,execv,execlp,execvp这四个函数的环境变量都会继承父进程的环境变量,环境变量也就不会改变,想要添加或者修改可以是用putenv来添加或者修改。
没有则添加,有则修改。
示例:
cpp
putenv("wjp=666");
如果需要覆盖使用自己的环境变量可以使用execle和execve带"e"的接口。
现在可以理解为什么在 bash 中创建程序并运行,程序能继承 bash 中的环境变量表了
在 bash 下执行程序,等价于在 bash 下替换子进程为指定程序,并将 bash 中的环境变量表 environ 传递给指定程序使用
其他没有带 e 的替换函数,默认传递当前程序中的环境变量表
exec系列的所有可变参数列表最后都会传给替换程序的main函数的argv数组
3.小结
这些函数原型看起来很容易混,但只要掌握了规律就很好记。
l(list) : 表示参数采用列表,就需要将指令选项一个一个传递过去
v(vector) : 参数用数组,把指令选项放入到自己定义的argv数组里面,传递。
p(path) : 有p自动搜索环境变量PATH,系统指令可以不用加路径。
e(env) : 表示自己维护环境变量。
exec系列的所有可变参数列表最后都会传给替换程序的main函数的argv数组