Linux进程替换(上)
💦 为什么要进程替换 && 什么是进程替换
创建子进程的目的:
-
执行父进程的部分代码。
我们之前所写的代码都属于这种情况。
-
执行其它程序的代码。
不要父进程的代码和数据。所以我们要学习进程替换。
所以进程替换是为了子进程能够执行其它程序的代码;进程替换就是以写时拷贝的策略,让第三方进程的代码和数据替换到父进程的代码和数据,给子进程用,因为进程间具有独立性,所以不会影响父进程。以前我们说数据是可写的,代码是不可写的,现在看来,确实如此。但是接下来要把其它程序的代码通过进程替换放在内存里让子进程与之关联,此时就要给代码进行写时拷贝。99% 的情况是对数据进行写时拷贝,1% 的情况是代码依旧是只读,本质就是对父进程不可写,子进程后续调用某些系统调用,实际给子进程重新开辟空间把新进程的代码加载,不让子进程执行父进程的代码。
💦 替换原理
我们想让子进程里执行新的程序,可以一步到位在内存里重新开辟两块空间以加载新程序的代码和数据,再修改子进程页表的映射关系,之后父子就彻底脱离了。
系统是如何做到重新建立映射关系的呢 ???
当子进程里要加载新进程时,操作系统可以设置一些特殊信号让该进程对全部代码和数据的写入,子进程会自动触发写时拷贝,重新开辟空间,再重新把代码和数据加载。
在进行进程替换时,有没有创建新的进程 ??
我们并不需要重新开辟新的 PCB、地址空间、页表,没有创建新进程的最有力证据是 pid 没变。所以我们曾经说过,程序要运行起来,必须先加载到内存,这句话当然没问题。但是反过来,程序只要加载到内存了,一定是变成一个进程,这句话有纰漏,因为进程是否是新创建是不一定的。不过大部分情况下是创建新进程的,进程替换是属于少数。
所以进程替换不会改变进程内核的数据结构,只会修改部分页表数据,然后把新进程的代码和数据加载至内存,重新构建页表映射关系,和父进程彻底脱离。
💦 替换函数
其实严格来说有7种以exec开头的系列函数,统称exec函数:
#include<unistd.h>
int execl(const chaar* path, const char* arg, ...);
int execlp(const char* file, const chr* arg, ...);
int execv(const char* path, char* const argv[]);
int execvp(const char* file, char* const argv[]);
int execle(const chra* path, const char* arg, ..., char* const envp[]);
int execve(const char* path, char* const argv[], char* const envp[]);
int execvpe(const char* file, char* const argv[], char* const envp[]);
这些函数的功能都是一样的,如果用 C++ 去设计这样的接口,一定是重载。这里是使用 C 去设计的,函数名的命名也有区分。下面我们会对这些接口进行演示,但实际在后面常用的也只是部分而已。
为什么 execve 要单独拎出来 ❓
虽然头文件都是 <unistd.h>,但实际上真正是系统提供函数只有 execve,其余的 6 个都是封装的,最后底层调用的依旧是 execve,这样做的原因是需要根据不同的用户来定制不同的使用场景。
好比,大家最后吃的米饭都会转换成能量,但是有的人喜欢蛋炒饭、有的人喜欢肉丝炒饭。
- 点赞
- 收藏
- 关注作者
评论(0)