深入理解进程(七)——程序替换
一、什么是程序替换?
用一个新的程序去替换一个进程正在调度的程序的信息。
二、程序替换的原理
进程运行一个程序时,我们需要将该程序加载到内存中,然后再通过该进程的虚拟地址空间利用页表映射到数据实际上存放的物理内存中,通过这样的方式,就实现了进程与程序之间的关联。如果我们此时将另一个程序加载到内存中,然后修改这种映射关系(也就是修改页表),那么进程就与这个新程序关联起来,那么该进程就不再执行原来的程序,而是会去执行这个新的程序。
三、程序替换的本质
本质上就是去替换一个进程pcb在内存中的对应的代码和数据(加载新程序到内存——>更新页表信息——>初始化虚拟地址空间),然后这个进程pcb重新开始执行这个新的程序
四、如何进行程序替换
我们进行程序替换的方式是使用exec函数簇。该函数簇中包含了六个函数。分别是下面六个:
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 execve(const char *path, char *const argv[], char *const envp[]);
execl函数
(1)函数原型
int execl(const char *path, const char *arg, ...)
(2)函数参数
该函数的参数的参数个数是不定的。
第一个参数: 想要替换的程序的路径(就是使用这个程序去替换正在运行的程序)
最后一个参数: NULL,作为运行参数结束的标志
其余参数: 数量不定,都是作为新程序的运行参数而传入的
(3)举个栗子
第一个cpp程序:test.cpp
#include <iostream>
#include <unistd.h>
using namespace std;
int main()
{
cout<<"hello"<<endl;
execl("./execl","ls","-l",NULL);
return 0;
}
第二个cpp程序:execl.cpp
#include <iostream>
using namespace std;
int main(int argc,char* argv[])
{
for(int i=0;i<argc;i++)
{
cout<<"argv["<<i<<"]="<<argv[i]<<endl;
}
return 0;
}
运行结果:
程序解析:
test.cpp
中输出hello后,进行程序替换,于是执行execl
这个程序,而execl
程序的功能就是打印出自己的运行参数,所以它就根据execl
函数知道传入的运行参数有两个。
execv函数
(1)函数原型
int execv(const char *path, char *const argv[]);
(2)函数参数
第一个参数: 同execl第一个参数
第二个参数: 一个字符串数组。这个字符串数组中存放的字符串就是作为新程序的运行参数而传入的,以NULL结尾。同execl相比,该函数是先将运行参数全部写在一个字符串数组中,然后再将该字符串数组传入函数中。
五、这六个函数的区别?
大家肯定很奇怪,明明有六个函数,但是为什么在上面我只讲了两个,因为这六个函数如果仔细观察的话就会发现
它们之间是有规律的,而这种规律就能帮助我们掌握其余的四个函数。
(1)函数名中有l和有v的区别
l是通过不定参来赋予新程序的运行参数
v是通过字符串指针数组来进行赋值的
(2)函数名中是否有p的区别
在于第一个参数的不同,如果没p,那么需要提供一个带路径的文件名;如果有p那么只需要提供文件名称即可,因为它默认会去PATH环境变量的路径中去寻找
(3)函数名中有没有e的区别
表示这个进程的环境变量需不需进行初始化,如果有e则表示需要初始化;没有e则表示使用默认的环境变量
六、shell是如何响应用户的命令
比如,你在命令行中输入语句ls -l
,然后显示出了一大堆的文件信息,那么Linux是如何执行它的呢?
首先你要知道一点,实际上ls
也是一个程序,和你自己写的程序没区别。当我们输入ls时其实就相当于运行自己的程序一样,而这个程序的父进程就是shell程序。
当我们输入一个命令ls
时,shell程序会对你的输入进行解析,然后创建一个子进程,而这个子进程就是通过程序替换来运行这个新程序ls
的,而程序的替换的方式就是通过上面的exec函数簇,而-l
也会被作为运行参数传入
七、注意
-
原先的代码和数据是否还存在?
一般来说,我们是调用fork函数之后,在子进程中使用程序替换。所以原先的代码和数据实际上是属于父进程的,因此它们是否存在也取决于父进程。如果父进程还在使用它们,那么就还存在;如果父进程不再使用它们,那么操作系统就会将其释放掉。
-
程序替换之后,执行完新程序之后,也是不会去执行原程序的。换句话说,在原程序调用exec函数的地方之后的语句是不会执行的。
-
上面的六个函数执行成功的时候是没有返回值的,只有当执行失败的时候,返回-1