🔥🔥 欢迎来到小林的博客!!
🛰️博客主页:✈️小林爱敲代码
🛰️博客专栏:✈️Linux之路
🛰️社区 :✈️ 进步学堂
🛰️欢迎关注:👍点赞🙌收藏✍️留言
💖程序替换的概念
目前,我们执行的子进程,都只能让子进程执行父进程的代码。那么我们想要让 子进程去执行一个全新的进程,那么这就是程序替换。 程序替换不会再创建新的进程,仅仅只是修改子进程的代码和数据。
💖程序替换的原理
进程替换的本质就是,替换代码和数据! 我们都知道子进程会继承父进程的代码和数据,而如果子进程的数据发生修改。那么父进程会写实拷贝一份数据,然后把拷贝的数据交给子进程。
而程序替换的本质,就是替换子进程的代码和数据!随后再更新一下页表的一些数据即可,也就是说,不用去修改PCB!只需要替换页表映射数据和代码的物理内存即可! 把子进程的代码和数据替换成全新进程的代码和数据,那么全新进程从哪里获取呢? 全新进程是从磁盘上获取的!然后把子进程的代码和数据替换成全新进程的即可。
但是,数据发生修改时进行了写实拷贝。但是子进程和父进程的代码是共享的!如果这样直接替换,那么父进程的代码不也被替换掉 ?所以:在正常情况下,子进程和父进程共用同一段代码!但是如果子进程发生了程序替换。那么父进程的代码同样会写实拷贝一份!因为进程之间是相互独立的。
所以正确的情况应该是如下图这样:
当然。页表也会进行调整。
💖进程替换函数
我们知道了进程替换是什么,进程替换的原理。那么怎么实际运用到我们的代码上呢?那么我们就需要介绍一些exce替换函数。
用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种exec函数 以执行另一个程序。当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动 例程开始执行。调用exec并不创建新进程,所以调用exec前后该进程的id并未改变。
而我们exce替换函数有七种,其中有一种是系统调用。所以我们这里只介绍六种。
#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[]);
函数解释
- 这些函数如果调用成功则加载新的程序从启动代码开始执行,不再返回。
- 如果调用出错则返回-1
- 所以exec函数只有出错的返回值而没有成功的返回值。
函数命名规律
这些函数看起来好复杂好难记,其实是有规律的。
- (list) : 表示参数采用列表
- v(vector) 参数用数组
- p(path) : 有p自动搜索环境变量PATH
- e(env) : 表示自己维护环境变量
函数名 | 参数格式 | 是否带路径 | 是否使用当前环境变量 |
---|---|---|---|
execl | 列表 | 不是 | 是 |
execlp | 列表 | 是 | 是 |
execle | 列表 | 不是 | 不是,需要自己组装环境变量 |
execv | 数组 | 不是 | 是 |
execvp | 数组 | 是 | 是 |
execve | 数组 | 不是 | 不是,需要自己组装环境变量 |
接下来我们依次演示这6个函数。
execl
int execl(const char *path, const char *arg, …)。第一个参数传的是文件所在路径,第二个是一个可变参数列表。根据的在命令行学的命令挨个填。
比如你想程序执行 ls -a -1,你只需要:
execl "文件路径","命令1","命令2" ..... NULL
我们写一个myload程序演示一瞎。
#include<stdio.h>
#include<unistd.h>
int main()
{
unsigned int pid = fork();
if(pid == 0)
{
//child
printf("i am child\n");
execl("/usr/bin/ls","ls","-a","-1",NULL);//execl "文件路径","命令1","命令2" .....NULL
printf("hhhhhhhhhhhhhh\n");
printf("hhhhhhhhhhhhhh\n");
printf("hhhhhhhhhhhhhh\n");
printf("hhhhhhhhhhhhhh\n");
printf("hhhhhhhhhhhhhh\n");
printf("hhhhhhhhhhhhhh\n");
exit(0);//如果execl调用失败,那么子进程直接终止。
}
sleep(1);
printf("i am father\n");
return 0;
}
然后我们运行一下这个程序。
我们可以发现子进程并没有打印下列一大串的hhhhhhhhhhhhhhh。因为子进程的代码已经被替换成ls的代码了。
execlp
execlp 和 execl 可以说百分之95的相似度了。知识execlp可以不用带全路径,只要是存在在环境变量中的,就可以直接输入命令找到环境变量。
代码演示:
#include<stdio.h>
#include<unistd.h>
int main()
{
unsigned int pid = fork();
if(pid == 0)
{
//child
printf("i am child\n");
execl("ls","ls","-a","-1",NULL);//execl "环境变量","命令1","命令2" .....NULL
printf("hhhhhhhhhhhhhh\n");
printf("hhhhhhhhhhhhhh\n");
printf("hhhhhhhhhhhhhh\n");
printf("hhhhhhhhhhhhhh\n");
printf("hhhhhhhhhhhhhh\n");
printf("hhhhhhhhhhhhhh\n");
exit(0);//如果execl调用失败,那么子进程直接终止。
}
sleep(1);
printf("i am father\n");
return 0;
}
结果还是一样
注意,一定是要是环境变量中的。否则无法找到对应的路径。
execle
这个需要自己组装环境变量,那么我们再写一个myexe.c的程序。
#include<stdio.h>
int main()
{
//就遍历一下环境变量
extern char ** environ;
int i = 0;
while(environ[i])
{
printf("%s\n",environ[i]);
++i;
}
return 0;
}
这个程序很简单,就是打印一下环境变量。
然后我们运行这个程序
我们可以发现,它打印了所有的环境变量,那么我们再来修改一下我们的 myload文件。
#include<stdio.h>
#include<unistd.h>
int main()
{
unsigned int pid = fork();
if(pid == 0)
{
//child
printf("i am child\n");
char *env[] =
{
"A = AAAAAAAAAAAAAAAAA",
"B = BBBBBBBBBBBBBBBBB",
"C = CCCCCCCCCCCCCCCCC",
NULL
};
execle("./myexe","myexe",NULL,env);//传自己组装的环境变量
printf("hhhhhhhhhhhhhh\n");
printf("hhhhhhhhhhhhhh\n");
printf("hhhhhhhhhhhhhh\n");
printf("hhhhhhhhhhhhhh\n");
printf("hhhhhhhhhhhhhh\n");
printf("hhhhhhhhhhhhhh\n");
exit(0);
}
sleep(1);
printf("i am father\n");
return 0;
}
这时候我们运行myload试试。
这时候我们的子进程就打印了父进程组装的环境变量。
execv
而v则代表数组,也就是说传一个数组进去。
#include<stdio.h>
#include<unistd.h>
int main()
{
unsigned int pid = fork();
if(pid == 0)
{
//child
printf("i am child\n");
char *avg[] =
{
"ls","-a","-1",NULL
};//命令数组
execv("/usr/bin/ls",avg);//传数组进去
printf("hhhhhhhhhhhhhh\n");
printf("hhhhhhhhhhhhhh\n");
printf("hhhhhhhhhhhhhh\n");
printf("hhhhhhhhhhhhhh\n");
printf("hhhhhhhhhhhhhh\n");
printf("hhhhhhhhhhhhhh\n");
exit(0);
}
sleep(1);
printf("i am father\n");
return 0;
}
execvp
和execlp一样,只不过把列表换成数组。
#include<stdio.h>
#include<unistd.h>
int main()
{
unsigned int pid = fork();
if(pid == 0)
{
//child
printf("i am child\n");
char *avg[] =
{
"ls","-a","-1",NULL
};
execvp("ls",avg);//传一个数组
printf("hhhhhhhhhhhhhh\n");
printf("hhhhhhhhhhhhhh\n");
printf("hhhhhhhhhhhhhh\n");
printf("hhhhhhhhhhhhhh\n");
printf("hhhhhhhhhhhhhh\n");
printf("hhhhhhhhhhhhhh\n");
exit(0);
}
sleep(1);
printf("i am father\n");
return 0;
}
execve
同理,和execle一样,只不过列表变成了数组传递。
#include<stdio.h>
#include<unistd.h>
int main()
{
unsigned int pid = fork();
if(pid == 0)
{
//child
printf("i am child\n");
char *env[] =
{
"A = AAAAAAAAAAAAAAAAA",
"B = BBBBBBBBBBBBBBBBB",
"C = CCCCCCCCCCCCCCCCC",
NULL
};
char *avg[] =
{
"myexe",NULL
};
execve("/myexe",avg,env);
printf("hhhhhhhhhhhhhh\n");
printf("hhhhhhhhhhhhhh\n");
printf("hhhhhhhhhhhhhh\n");
printf("hhhhhhhhhhhhhh\n");
printf("hhhhhhhhhhhhhh\n");
printf("hhhhhhhhhhhhhh\n");
exit(0);
}
sleep(1);
printf("i am father\n");
return 0;
}
以内建方式进行运行!
不创建子进程,让父进程自己执行
chdir
{
"myexe",NULL
};
execve("/myexe",avg,env);
printf("hhhhhhhhhhhhhh\n");
printf("hhhhhhhhhhhhhh\n");
printf("hhhhhhhhhhhhhh\n");
printf("hhhhhhhhhhhhhh\n");
printf("hhhhhhhhhhhhhh\n");
printf("hhhhhhhhhhhhhh\n");
exit(0);
}
sleep(1);
printf("i am father\n");
return 0;
}