【进程控制⑥】:进程替换/ exec*()系列接口
一.进程替换原理
我们的程序是可以将系统调用接口给封装起来,调用我们的程序本质就是调用了系统接口。
那么如何进行封装呢?通过exec*系列函数可以完成进程的程序替换,比如下面的是是比较标准的写法:
第一个参数是程序的路径,第二个参数就是程序名,而后面的参数就是可选择参数。
就比如这句代码将要把该进程替换成
/user/bin/ls路径下的程序名为ls的进程,并带上-a,-l选项。
然后当我们调用我们的程序时,就会调用到ls -a -l命令。
替换的原理是什么呢?
要知道可执行程序和文件都存储在磁盘中,而我们的程序的代码和数据则已经加载到内存中了。execl的方法非常暴力,直接替换。什么意思呢?就是直接将磁盘里的可执行程序(要替换的程序)的代码和数据替换现在进程的的代码和数据。替换完后,进程并没有被影响,该调用就调用,只不过物理内存里的地址发生改变了。虚拟地址并没有改变。
二.替换特点
替换的原理非常简单粗暴,那这样会不会出现问题呢?
上面的情况是单进程,如果是多进程如何进行程序替换呢?
多进程通常是让子进程进行程序替换,而父进程则等待子进程。
1.独立性
子进程在进行程序替换时,会不会影响父进程呢?为什么这样说呢?因为父子进程共用同一块代码和数据,当子进程被替换成新的可执行程序时,它的代码和数据也就被替换了,那父进程的代码和数据呢?
答案:当然不会!因为进程之间是具有独立性的---->[写时拷贝]
在子进程没有执行程序替换之前,与父进程共享代码和数据,当子进程执行程序替换时,需要将新的程序的代码和数据覆盖父进程的代码和数据,但不要慌!为什么呢?因为存在写时拷贝!
当往父进程的数据里写入时,就会发生写时拷贝,重新申请一块内存给子进程写入,所以父进程的数据并没有被覆盖掉。那代码呢?
代码有没有被覆盖掉呢?其实代码也存在写时拷贝,也没有被覆盖掉。
2.唯一性
程序替换有没有创建新的进程呢?
答案:并没有创建新的进程!只是进行进程的代码和数据的替换工作,并不会创建进程。原来的进程还是原来的PCB和进程地址空间和页表。只不过物理内存发生改变了,只需要改变一下页表的映射关系。
3.不变性
当子进程发生程序替换后,将会发现子进程的环境变量还是原来的环境变量,并没有被覆盖掉。我们知道子进程的环境变量是继承父进程的,那环境变量是什么给进程的呢?因为环境变量也是数据,创建子进程的时候,环境变量就已经被子进程继承下去了,当发生替换后,子进程的环境变量不变,说明程序替换是不会将环境变量替换的。
如果我们非要替换环境变量有没有方法呢?
当然有
①因为子进程是继承父进程的,所以我们只要修改父进程的环境变量就可以更新子进程的环境变量。
②或者使用带有e的exec*系列函数接口,这个是要自己传递环境变量,并且会覆盖原来的环境变量
4.不返回
还有就是进程程序替换成功后,就不会再返回,也就是exec*函数后面的代码就不会执行了,如果程序替换失败才会返回,后面的代码才会执行,所以这个就可以作为程序替换是否成功的判断条件,当替换失败了,就会执行子进程后面的代码,后面的代码肯定会有退出exit(),我们只要设置对应的退出码即可判断。
三.程序替换应用
程序替换的系统调用接口大概有7个,这里介绍经常使用的6个系统调用函数。
【exec*系列系统调用】
注意以上函数基本上第一个参数都是程序的路径,为什么要程序的路径呢?你要替换这个程序,也就是要执行这个程序,你执行这个程序不应该要先知道这个程序在哪吗?所以程序的路径是必须要有的。当找到这个程序之后呢?我们是不是就要思考,怎样执行这个程序呢?是以什么方式执行呢?而后面的参数就是用来传不同的参数使程序以想要的方式执行。[即命令行参数]
①execl:
--->'l' 注意这个l是什么意思呢?我们可以看成list链表,为什么呢?因为它后面的参数可以像链表一样,一个一个链接起来,不是一个整体。而如何使用呢?命令行怎么写的,你就怎么传就可以了。
②execlp:
'p',注意这个p是什么意思呢?这个p可以理解为环境变量中的PATH。默认路径,你注意到没它的第一个参数不是路径而是程序文件名,这说明像这样的函数我们不需要传路径,系统会到默认路径PATH里去找,只需要写要执行的文件名即可。
③execv:
'v',注意这个v是什么意思呢?这个v可以理解为vector数组,为什么这样说呢?因为它的后面的参数可以放进一个数组统一传过来。而不像list那样一个接着一个。
要注意理解:当我们调用exec*系列系统调用接口时,我们传的命令行参数,会被系统自动传给要替换的程序的main函数。要替换的程序的main会接收这些命令行参数的。
④execle:
'e',注意这个e是什么意思呢?这个e其实是环境变量env。我们进行程序替换时,也可以传递环境变量给要替换的程序。
exec*接口不仅可以替换那些已经存在的可执行程序,还可以调用我们自己写的可执行程序,就比如用C去调用C++程序。为什么可以呢?因为一旦程序执行就会变成进程,而进程就可以被替换,就算是其他语言写的程序一旦变成进程也可以被替换调用。
我们可以用自己写的程序来替换子进程,这样就可以验证当一个程序被替换时,系统会自动将那些环境变量命令行参数都传给替换的程序的main函数。