进程替换的背景:
进程的替换我们需要调用execl这个接口,excel在3号手册,属于c库函数接口。
调用系统命令
execl
为了方便理解execl的作用,我们写一个程序:
单进程替换
我们发现运行结果是通过c库里的exec接口把系统命令 "ls"调用起来了,并且是ls带参的 ls-a-l-n形式
也就是 用ls替换了原本的程序。
替换原理
我们的程序跑起来之后就会变成进程,是进程就有PCB,地址空间,页表,物理内存。
同时ls命令也是一个文件,在磁盘当中。
它有代码和数据组成。
我们调用execl,就把ls的代码数据替换了我们原来的代码,数据,完成了进程替换:
多进程替换
我们让子进程进程ls替换,父进程以阻塞式等待子进程,等待成功把pid打印出来:
观察运行结果发现,子进程仍然没有打印出“进程结束这句话”
另外,父进程等待成功之后打印出来的pid和子进程的pid一样,因此得出结论:子进程没有创建成功。
为什么不创建子进程,因为子进程是父进程的拷贝,共享一块物理地址,子进程如果被替换,父进程也要被替换,为了进程的独立性,父进程不能被替换。
为什么这句话打印不出来呢?
原因:
证明:
我们故意把execl调用写错,这样调用失败就可以按照源程序走,就可以把源程序的后续显示了:
我们可以把execl的返回值用n接受一下,看一下execl替换失败之后会返回什么:
execl替换失败之后会返回-1:
我们利用这一特性可以这样写:执行替换,替换成功原程序就不会再执行,替换失败原程序就exit退出。
execlp
为什么要写两个ls,第一个ls是我们要替换的文件名,第二个ls是参数的一部分。
execv
写法:
execvp
因为自带path,即系统自己会在环境变量路径下查找,我们直接输要替换的目标文件名):
我们也可以这样:因为argv[0]就是ls这个参数。
execle的引入
先不说execle的用法,先说说父子进程环境变量的继承关系。
父子环境变量的继承关系
环境变量具有全局属性,也就是环境变量可以被子进程获取。
我们现在有两个文件,一个myproce,一个mytest。我们让myproce去调用mytest。
此时,myproce的父进程是bash,子进程是mytest。
按照子进程会继承父进程环境变量这一说法,myproce应该继承了bash的环境变量,而mytest继承了myproce的环境变量。
证明
在命令行里写一个环境变量,这个环境变量由bash创建:
然后我们在mytest里面把环境变量打印出来,然后看看mytest里的环境变量里是否有bash创建的环境变量,如果有,说明继承了:
原理
通过地址进程空间,让子进程继承父进程的环境变量和代码数据:
环境变量被继承下去是一种默认行为,不受进程替换的影响。
程序替换不会替换新程序的环境变量,只会替换新程序的数据和代码。
execle的使用
现在我们可以用execle了,目的为了给子进程传递环境变量。
回忆一下,myproce是父进程,mytest是子进程。我们可以通过./myproce调用./test。
在myproce里用execle接口调用mytest:
在mytest里打印环境变量:
运行:
继承成功
同样的,我们创建个环境变量,用./myproce运行,子进程./mytest照样可以继承:
总结
子进程执行时候的环境变量从父进程那里来。
两种方式:1.父进程手动传 2.通过地址进程空间继承环境变量,直接用
如果子进程现在不想继承系统环境变量,想继承我们自己写的环境变量呢?
自己写环境变量传给子进程
我们上面学的execl,execlp,execv,execle等等最终都会转化为execve:
上文验证了execl可以把系统文件调用起来替换我们原来文件,那是否可以调用我们自己写的文件来替换呢?
调用c++
写一个mytest.cc文件,让其输出三行hello word:
在test6.cc里面调用mytest.cc:
写一个makefile:
运行结果:
结论:execl不仅可以调用系统文件进行替换,也可以调用我们自己写的程序进行替换。
调用脚本
创建一个test.sh文件,在里面写一段脚本:
用系统解释器bash解释:
输入ll,可以看见t1,t2两个文件已经创建好了:
但是我们发现他们是没有可执行权限的,我们给它加上权限就可以像正常程序一样执行了:
我们在test6.cc文件里调用一下这个脚本文件:
发现就可以用这个脚本替换原来的程序。
调用Python
然后我们在test6.cc里调用
一个程序的创建,执行,结束流程:
首先 创建pcb,地址空间,页表,但是页表此时没有映射,也就进程创建好了,但是还没开始执行代码。
现在要执行代码了,系统再调用exec*类似的接口把代码和数据从磁盘中加载进内存里,然后填入页表,完成映射。
解疑:
全局变量在被编译时磁盘中就有数据了。
因为我们定义全局变量会赋初始值 这个初始值和全局变量会在磁盘.data区域提前预制好,当代码被加载进内存时就加载进去。
局部变量,堆栈上的变量只有被加载时,变为进程时才会有数据,才会形成堆区,栈区
定义的常量字符串,会被编译进可执行程序里的read only这一块区域,在程序被加载内存时会被加载进代码区和全局数据区之间的只读区域。