本篇文章,继续来和大家分享与Linux相关的知识。本篇文章主要涉及的内容为进程替换的原理,进程替换函数的使用以及进程替换的演示
进程替换
我们了解进程替换的思路,如下图
单进程版
进程替换会用到函数execl,在man手册第三章
我们先来见见怎么使用什么是程序替换
这不就是我们用的ls指令吗?
我们试试top指令
通过ls指令和top指令的替换,我们不难发现,两个程序在运行的时候,都执行替换程序前的代码,但没有执行替换程序后面部分的代码。这是什么原因呢?我们来了解一下程序替换的原理。
进程替换的原理
通过前面的文章,我们了解,一个进程被创建好后,它会有自己的PCB,在Linux中就是task_stuck。它还会有自己的进程地址空间mm_sturck,和一个页表,页表的中会有一列属性用来存放虚拟地址,在这里列属性的左边会有一列属性用来存放物理地址。物理地址指向进程加载到物理内存中的代码和数据。在物理内存的代码数据只是程序的一部分,程序的所有代码数据都存放在磁盘中,只有再需要的时候,才会从磁盘把代码数据加载到内存中。ls指令,本质也是一个可执行程序,它在磁盘中也存放着它的代码和数据。当调用execl函数时,系统会将ls指令的代码数据加载到内存中,把原有的代码和数据替换掉。这时候,就会有人问了?如果ls指令要加载的代码和数据比较大或比较小怎么办?大了,系统会开辟新的空间,并更改物理地址。小了,系统则会释放掉多余的空间。原来的代码都被替换掉了,也就是没有了。所以,进程替换之后的代码当然不会执行啦,也执行不了。我们可以通过观察,程序执行进程替换之后的代码,来判断进程替换是否成功。
进程替换,并不会创建新的进程,我们利用监控脚本来验证,为了便于观察,代码调整为下图
自程序运行起,只有一个进程9569存在
进程在替换成功之后,CPU会从头开始执行新的程序。这时,或许会有人问,那CPU怎么知道程序的入口地址?在Linux中,可执行程序文件是有格式的,是ELF格式。该格式的表头会存放不同区域的入口地址。通过表头的入口地址,CPU就可以找到程序的入口地址了。
七个进程替换函数
在理解完原理后,我们可以感觉到进程替换函数,起加载器的效果,把我们需要执行的程序,加载到内存中。进程替换函数一有七个,六个是库函数,一个是系统调用。我们这里以库函数为例,通过库函数讲清楚使用原理,系统调用那个自然也就会了。
我们仔细观察,不能发现这七个函数以exec*开头
第一个execl
execl这个函数带了一个l,l是list的意思。这说明它采用可变参数方式,是像链表节点一样,一个一个的进行传参,最后NULL结尾。下图中的写法,是标准写法。
大家思考一个问题,我们要执行一个程序第一件事情是什么?当然是先找到这个程序在哪里啦!那找到之后呢?找到之后,要解决的问题是如何去执行这个程序,主要是要不要涵盖选项,涵盖那些?
此时,我们再去理解execl函数,就变得简单了。execl的第一个参数,传绝对路径,我们要告诉execl,要替换的程序在哪里。第二个参数,execl找到这个函数后,你希望它怎么执行。我们平时怎么执行命令的,你就这么传,最后,以NULL结尾就可以了。
第二个execlp
这个函数比execl多了一个p,p是PATH的意思。什么PATH?是不是跟环境变量PATH很像。它想表达的意思是,你不用像使用execl那样这么麻烦了,不用给我传什么绝对路径,你只需要告诉我,你要替换的程序叫什么就可以了,我会到PATH这个环境变量中的默认路径中去找。
execlp第一个参数,虽然,只传要替换的程序的名字,但本质还是告诉函数这个程序在哪里。第二个参数,我们命令行如何执行命令,这里就这么传,最后以NULL结尾
第三个execv
这个函数带的不是l,而是v。v是vector的意思。可以理解为数组的意思,这里想表达的意思是,你以指针数组的方式告诉我怎么去执行指令。
execv的第一个参数,同样的使用绝对路径告诉我,要替换的程序在哪里。第二个参数传一个以NULL结尾的字符串数组过来,告诉我怎么去执行程序。
第四个execvp
execvp这个函数,相信我不讲你都会。
第一个参数,我们只需要告诉execvp,我们要替换的程序的名字即可
第二个参数,把以NULL结尾的字符串数组传给execvp,告诉它怎么执行这个程序。
第五个execle
execle这个函数,带个e。e就是env环境变量的意思。在这里的意思是,你除了告诉我,程序在哪里,怎么执行,还应该给我传一张环境变量表。
第六个execvpe
execvpe这个函数,带v,带p,带e,我们已经了解它的意思。这里就不多说了。第一个参数,传要替换的程序的名字,第二个参数传字符串数组,第三个参数传环境变量表。
第七个execve
说到这,你如果不会知道怎么用这个函数,建议从第一个开始,再看一遍
多进程版
execl,execlp,execv,execvp这个四个函数,除了用法有点不同,执行结果都一样,这里我们就以execl演示
我们可以看到子进程进程替换后,后续的代码没有执行。而父进程正常执行,也就是子进程的进程替换并不会影响父进程的运行。这是为什么?父进程创建子进程,它两共享一段代码。之前,我们说过程序一旦启动代码就不能修改,其实,这个得看是谁,如果是你肯定会报错。但操作系统来改,就不会,在它要修改时,发现这段代码父子共享。于是,发生写时拷贝,为子进程拷贝一份代码后,才进行进程替换。
execl,execlp,execv,execvp这四个函数可以执行系统的指令,那它们能不能执行我们的指令,答案是可以的。
我们用C++简单写一个源文件,.cc、.cpp、.cxx都是C++源文件后缀名,只是.cpp比较常用
修改一下execl调用自己的程序
编译运行,就可以把我们自己的程序调用起来了
除了可以调用C++写的程序,还可以调用JAVA和脚本语言写的程序,比如bash,python
我们以bash和python为例
bash,我们需要先创建一个.sh后缀的文件,sh是shell的简写。所谓脚本语言,就是把命令行写的指令放到一个文件里进行执行
程序一调用,结果就出来了
再来看看python,这里我们需要创建一个.py为后缀的文件
修改完调用,编译后,程序就能跑了
思考一个问题,无论是我们的可执行程序,还是脚本,为什么可以跨语言跑呢?因为所有语言运行起来,本质都是进程。
ls指令,本质也是个可执行程序,它也会有main函数,execl的第二个参数的内容,实际上是会传给我们要替换的程序的main函数。
我们来验证一下
调用的时候,我们故意加上一些选项
编译运行,我们就可以看到我们刚刚传的参数了
execle,execvpe,execve这三个函数,执行起来的结果和上图一样。我们主要演示另外一个东西,环境变量。
环境变量,我们是熟知的,既是不传,子进程也会继承父进程的环境变量。
可我们就想传,我们可以使用带e的函数来传环境变量,这里我们以execvpe函数为例,调用otherExe
在otherExe.cpp中增加打印环境变量信息的功能
编译运行,环境变量的信息就被打印出来了
那我们如何新增环境变量呢?
第一种:直接export增加环境变量
如果你只是想给当前进程增加一个环境变量,则调用putenv函数
通过grep就可以查到我们增加的环境变量
我们可以传系统提供的环境变量,那我们可不可以传自己定义的环境变量表,我们试试
编译运行,我们自定义的环境变量表被打印出来,但原来系统提供的环境变量信息没有了,这里的环境变量的传递,采用的策略是覆盖,而不是追加
好了,到这里,我们本次的分享就到此结束了,不知道我有没有说明白,给予你一点点收获。关于更多和Linux相关的知识,会在后面的文章更新。如果你有所收获,别忘了给我点个赞,这是对我最好的回馈,当然你也可以在评论发表一下你的收获和心得,亦或者指出我的不足之处。如果喜欢我的分享,别忘了给我点关注噢。