进程程序替换
替换概念
当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的代码部分开始运行。调用exec并不创建新的进程,所以调用exec前后该进程的id并未改变。
大概意思就是程序替换只是把程序换了,仅仅是从1号程序换到了2号程序,修改了页表的映射地址,但是并没有创建新的PCB,因此没有创建新的进程。
这就是进程替换的概念!
替换函数
在linux手册里,有6个相似的函数,他们虽然参数列表不一样,但是功能一样。
- 如果这些函数调用成功则加载新的程序,从启动代码开始执行,不再返回。
- 如果出错则返回-1。
- 所以exec函数只有出错的返回值而没有成功的返回值。
execl()
l:列表的形式传入参数
int extcl(const char * path, const char* argv,...);
参数:
path:是要打开可执行程序的路径
argv,... :这个语法表示可变的参数列表,可以传入多个不定个数的参数,最后一个参数传入NULL,表示参数传递完成。
描述不清楚,看个例子就明白了:
cpp
1 #include<stdio.h>
2 #include<unistd.h>
5 int main()
6 {
7 printf("当前进程开始!\n");
8 //execl("/usr/bin/ls","ls","--color=auto","-l",NULL);
9 //一旦调用成功,后续代码都不会执行了!!!
10 execl("/usr/bin/ls","ls","--color=auto","-l", "-i",NULL);
11 //execl("/usr/bin/top", "top", NULL);
12 printf("当前进程结束!\n");
13 return 0;
14 }
因为Linux系统下一切皆文件,ls命令其实就是一个可执行程序,我们使用execl函数,执行这个程序。我们可以改变可变参数列表来使用ls命令不同的功能。
execv()
v:数组的形式传入参数
int extcv(const char * path, char *const argv[]);
参数:
path:仍然是文件路径。
char *argv[]:指针数组,以数组的形式传入。
为了更加适应实际的应用场景,我们使用父进程fork子进程,然后使用子进程进行进程替换,这样不会影响父进程的执行。
如下:
cpp
7 #define NUM 32
8 int main()
9 {
10 pid_t id = fork();
11
12 if(id == 0)
13 {
14 //子进程
15 // ls -a -l
16 printf("子进程开始执行,pid = %d \n",getpid());
17 //传入指针数组
18 char *const _argv[NUM] = {
19 (char*)"ls",
20 (char*)"-l",
21 (char*)"-i",
22 NULL
23 };
24 execv("/usr/bin/ls",_argv); //传入指针数组!!!
25 exit(1);
26 }
27 else{
28 //父进程
29 printf("父进程开始等待,pid =%d \n", getpid());
30 int status = 0;
31 pid_t ret = waitpid(-1, &status, 0); //阻塞等待
32 if(ret>0)
33 {
34 printf("等待成功,退出码为:%d \n", WEXITSTATUS(status));
35 }
36 }
37 return 0;
38 }
可以看到,子进程帮助父进程完成了执行ls命令,并且没有影响父进程的执行。
execvp()/execlp()
p:表明第一个参数不再是文件路径,而是通过环境变量就可以找到的程序。
看下面的例子更能清楚的解释:
execvp()的例子:
cpp
7 #define NUM 32
8 int main()
9 {
10 pid_t id = fork();
11
12 if(id == 0)
13 {
14 //子进程
15 // ls -a -l
16 printf("子进程开始执行,pid = %d \n",getpid());
17 //传入指针数组
18 char *const _argv[NUM] = {
19 (char*)"ls",
20 (char*)"-l",
21 (char*)"-i",
22 NULL
23 };
24 //execv("/usr/bin/ls",_argv);
25 execvp("ls",_argv); //因为环境变量的存在,我们可以只输入ls便可找到该文件。
26 exit(1);
27 }
28 else{
29 //父进程
30 printf("父进程开始等待,pid =%d \n", getpid());
31 int status = 0;
32 pid_t ret = waitpid(-1, &status, 0); //阻塞等待
33 if(ret>0)
34 {
35 printf("等待成功,退出码为:%d \n", WEXITSTATUS(status));
36 }
37 }
38 return 0;
39 }
execlp()例子:
仅仅将上面的代码,25行换成26行的内容即可!
cpp
25 //execvp("ls",_argv); //因为环境变量的存在,我们可以只输入ls便可找到该文件。
26 execlp("ls", "ls", "-l", "-i", NULL);
execle()/execvpe()
e:可以维护自己的环境变量,将环境变量传给要替换的进程。
以execle为例:
将上面的子进程调用部分修改:
cpp
7 #define NUM 32
8 int main()
9 {
10
11 //设置一个环境变量,为的是 让子进程拿到
12 //因为环境变量具有全局属性
13 char *const _env[NUM]={
14 (char*)"xty=123456789",
15 NULL
16 };
31 execle("./myexe", "mycmd", "-a", NULL, _env); //修改成这样,//其中列表参数是随便传的,myexe是调用的可执行程序。
cpp
myexe:
1 #include<stdio.h>
2 #include<stdlib.h>
3
4 int main()
5 {
6
7 printf("得到的环境变量xty:%s\n", getenv("xty"));
8 return 0;
9 }
可知myexe获取到了环境变量。
如何在C/C++程序里面执行别的语言写的程序。
bash
//使用这个命令即可运行
python test.py
首先写一个python文件脚本。待会我们就是用c程序执行它。
使用execlp()调用比较合适。
c
1 #include <stdio.h>
2 #include<stdlib.h>
3 #include<unistd.h>
4 #include<sys/types.h>
5 #include<sys/wait.h>
6 int main()
7 {
8 pid_t id = fork();
9 if(id == 0)
10 {
11 //子进程,调用别的语言写的程序
12 execlp("python", "python", "test.py", NULL);
13 exit(-1);
14 }
15 else{
16 //父进程
17 int status = 0;
18 printf("父进程开始等待: \n");
19 int ret = waitpid(-1, &status, 0); //阻塞等待
20 if(ret > 0)
21 {
22 printf("父进程等待成功,子进程退出码为:%d \n",WEXITSTATUS(status));
23
24 }
25 }
26 return 0;
27 }
我们可以看出python脚本被我们成功调用!
小tips
我们在运行py脚本时,需要运行python这个程序。因为python是一个解释器,我们需要使用解释器来解释test.py文件。
bash
python test.py
如果我们给该test.py文件加上执行(x)权限,那么我们这样即可运行该文件:
因此将上面的execlp调用参数,修改为下面这样也可以:
c
//12 execlp("python", "python", "test.py", NULL);
12 execlp("./test.py", "test.py", NULL);
上面就是程序替换的相关知识,更深入的内容还请读者自行查阅和学习。