【Linux庖换现象丁解牛】—进程程序替换!
1. 进程程序替换现象
我们先直接见一下进程程序替换的现象,至于下面我用到的函数我们暂时不用管理解。
#include <unistd.h>
#include <stdio.h>int main()
{printf("程序开始运行\n");execl("/usr/bin/ls","ls","-a","-l",NULL); printf("程序运行完毕\n");return 0;
}
我们用进程./code执行了系统级指令ls,这种现象就是进程程序替换,当然我们还可以替换其他指令!
2. 进程程序替换原理
我们观察上面的运行结果也会发现,进程发生程序替换后,进程运行的结果并没有【程序运行完毕】这个字符串,但是,在我们的代码中实际上是会打印这个字符串的!这就和进程程序替换的原理有关了:当我们调用了exec*系列的接口后,如果进程替换成功,那么原代码就会被替换进程的代码覆盖【也就是说原代码已近不存在了】。所以只要进程程序替换成功,无论之后我们写了什么代码,我们都不会执行了。
其实,如果进程替换失败,会返回-1值。
可是,如果我们不希望进程程序替换影响我们的原进程,怎么办呢?
很简单,我们仅需要创建一个子进程,用子进程来替换【代码和数据】。至于为什么这样做不会影响父进程:从宏观上来讲,进程具有独立性。具体一点的话,子进程的代码发生改变,那子进程也会发生写实拷贝【这和我们之前说过的数据改变发生写实拷贝的 原理相同】。既然如此,那我们也必须注意到一点,进程程序替换并没有创建一个新的进程,而只是改变了原进程中的代码,代码可能会更多,对应的地址映射可能也会发生改变,但这些我们并不关心。
那execl除了可以替换系统级的可执行程序,能不能替换我们自己写的程序呢!答案是肯定的。简单演示一下:
下面我在当前目录下写了一个C++程序,并在C程序中调用了该程序!
那该接口是否可以调用python语言呢?当然可以,不止如此,无论是脚本语言,还是编译型,解释型等等,只要在系统下执行,他都会最终变成一个进程加载到内存中。而exec*系列的接口是系统级别的,所以可以调用各种语言编写的程序!
3. exec*系列接口
接下来我们就来理解一下exec*系列接口。
首先就是我们刚刚用到的execl:
它的第一个参数就是路径,第二个参数是程序名称,我们注意到后面的三个...,在C语言中我们就知道这是可变参数【这部分参数的填写原则就是:指令怎么敲,参数怎么填】,不过,我们要最后填写一个NULL参数表明参数填写完毕!(l就是list的意思,表示参数列表)
execlp:
p即PATH环境变量,该接口在我们替换系统级指令时比较方便,因为我们仅需填写指令名,该接口就会自动去环境变量PATH指定的路径下去找!
execv:
v即vector,我们可以简单的理解为数组,第一个参数也是传路径,后面我们需要传一个命令行参数表,即指针数组,下面演示一下:
execvpe:
e即是environment,环境变量的意思。如果我们使用子进程用该接口进行程序替换,并且我们还传递了新的环境变量,那么子进程实际上就被要求使用全新的环境变量,子进程从bash中继承的旧环境变量全部被覆盖,而不是新增!下面就举一个栗子理解一下:
我们先写一个other程序,这个程序会打印自己的命令行参数表和环境变量表:
接下来,我们在code.c中创建子进程,使用execvpe接口用other替换该子进程。并且我们传递自己写的命令行参数表和环境变量表,看看结果。
实验和我们预期的一样,但是,如果我们想新增环境变量,保留原来的,我们要如何办到呢?
第一个做法就是不使用execvpe接口,我们使用其他接口配合putenv()接口就可以办到。
谁【进程】调用该接口导入环境变量,谁的环境变量表中就会新增自己写的环境变量!
那如果我非要用带e系列的接口,我该怎么办到呢?
其实也非常简单,既然我们传递的环境变量表是覆盖式的,那我们将environ传过去不就行了吗。在这之前,我们把自己写的环境变量用putenv提前导入不就行了吗!