目录
进程替换
1.定义:
2.为什么要进行进程替换?
3.怎样进行进程程序替换?
4.进程替换的原理:
原理总结:
5.Linux进程替换的函数:
5.1参数:
5.2函数返回值问题:
5.3:execl函数用例
5.3.2升级案例:
编辑
5.3.3调用execl函数失败的案例:
5.3.5 替换自己写的.c程序案例:
5.3.5 替换自己写的.cpp程序案例:
5.4调用execlp函数案例:
5.5调用execv函数案例:
5.6调用execle函数的案例:
5.6.2execle案例2:
execl系列函数总结:
进程替换
1.定义:
进程替换就是让一个进程从执行文件A的代码和数据转换到执行文件B的代码和数据,也就是说原本该进程执行的文件代码和数据会被另一个文件代码和数据替换掉,一般情况下,进程程序替换都不会使用父进程直接进行进程程序替换,而是让父进程调用fork函数去创建一个子进程,让子进程去执行一个新的程序即可。
2.为什么要进行进程替换?
我们直到一个可执行文件程序在执行的时候是父进程执行的,当文件中有多个循环while,for等代码时,父进程对其一个一个的执行就很浪费时间,若是碰上死循环就更麻烦了,所以当父进程调用fork后创建出了子进程,父子进程是同时执行程序,效率上方便很多。
现在需求增加了,不仅要让子进程能够执行父进程的代码块,也要让子进程能够做一些父进程不能做的事情——即执行一个全新的代码程序,这样父子进程做的事情就有所差异,使得父子进程的代码彻底分离,维护了进程的独立性。
进程替换在生活中的例子:当我们在网上做一些OJ编程题的时候(例如牛客网,leetcode等官网),我们写好的答案提交时,都会被传入到后台中,这时系统就会创建出子进程,让该子进程执行我们的代码进行检测,检测通过多少用例,有无bug/err等。
3.怎样进行进程程序替换?
在Linux中可以使用execl系列的多个函数进行进程替换。
4.进程替换的原理:
进程程序替换的原理:
假如刚开始父子进程都是执行程序my.exe,后来想让子进程执行b.exe了,此时就要进行进程程序替换,替换的过程就是先将b.exe从磁盘加载到内存,然后重新建立子进程的页表,更新子进程的页表的映射关系。(注:这里修改的是页表的物理地址而非虚拟地址,此时父子进程代码块中的虚拟地址是一样的,但是通过页表映射出的物理地址是不一样的,父子进程的代码是互不干扰的,满足了进程的独立性。——写实拷贝。
Linux进程地址空间——下篇_ 讲上面这个知识点时,需要了解进程地址空间和写实拷贝,左边这个链接中有讲到这部分知识,感兴趣的友友们可以看看。
原理总结:
1.子进程原本执行着和父进程一样的代码和数据,我们对子进程使用进程程序替换,也就相当于是修改代码和数据,那么系统会利用写时拷贝技术将my.exe的代码和数据拷贝一份, b.exe的代码和数据被加载到内存后,my.exe的代码和数据就会被覆盖成b.exe的代码和数据然后重新映射一份新的页表给子进程(进程独立性,一个进程的修改绝不能影响另一个进程!)
2.程序替换的本质:将指定程序的代码和数据加载到指定的位置,覆盖原来程序的数据。
5.Linux进程替换的函数:
可变参数列表就是可以在这些拥有...的函数中,增添多个类型的参数进行传递,没有数量、类型的限制。
5.1参数:
path: 欲替换程序的路径欲
file: 替换程序的名称
arg: 命令行参数
argv[]: 命令行参数数组 envp[] :环境变量表
注意:exec函数的参数必须以NULL结尾
5.2函数返回值问题:
这些函数若调用成功则加载新的程序井执行,不具有返回值。因为调用成功与否,我们从执行结果就能直接看出来,所以设置这个函数的大佬们就没有设置返回值。
5.3:execl函数用例
运行结果:
5.3.2升级案例:
运行结果:
程序在做这些替换指令的过程中,可以参照以前执行指令的形式ls -l -a ,以前在Linux中怎么传,现在在execl函数中就怎么传:execl("/usr/bin/ls","Is","-a","-I",NULL) ; 需要注意,传参完最后需要NULL结尾。
5.3.3调用execl函数失败的案例:
结果:程序仍执行原来的代码和数据。
5.3.4多进程的execl函数调用案例:
#include<std1o.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types .h>
#include<sys/wait.h>
#include<assert.h>int main(){int status=0;pid_t id=fork(); //子进程if(id==0){printf("I am sonProcess,I am sharing recourse with fatherProcess\n");execl("/usr/bin/ls","ls","-a","",NULL); //进程替换exit(100);}//父进程if(id>0){pid_t ret=waitpid(id,&status,0)://ret>0表示父进程已成功回收了子进程的资源和退出状态//ret==0表示子进程仍在进行,没有退出//ret<0表示fork了进程失败printf("我是父进程,pid:%d ppid:%d\n",getpid(),getppid());if(WIFEXITED(status)!=0){printf("了进程是正常结束!\n");printf("了进程退出码为:d\n"(status>>8)&OXFF);}else{printf("子进程是异常结束!\n");printf("sig code:%d\n",status & OX7F);sleep(3);}}
运行结果如下:
结果解析:从结果的最后一行可以看出,父进程获取子进程退出信息后展现出来的子进程退出码为0,但是在上方代码中,子进程执行的是exit(100); 子进程的退出码应为100才对,为何是0?
真正的原因就是:子进程执行了execl函数后,处于execl后面的所有代码数据都会被execl首个参数指定的文件给覆盖掉,exit(100);被覆盖掉了,然后子进程执行完execl执行的指定文件后就正常退出了,所以子进程的退出码为0
5.3.5 替换自己写的.c程序案例:
运行结果:
5.3.5 替换自己写的.cpp程序案例:
总结:execl函数可以替换任何类型语言(py,java,php等)的可执行程序文件。
5.4调用execlp函数案例:
在上个多进程的案例基础下,修改execl函数即可。
execlp与execl函数的不同在于:多了个p,p的功能在于不用告诉系统该文件的路径,只需要将文件名称写上去就行,系统会自动去磁盘中寻找。
5.5调用execv函数案例:
execv函数中,v指代vector(相当于数组),可以将所有的执行参数,放入数组中,统一传递,而不用进行使用可变参数方案。
如上代码中,就是将ls指令及其后面的选项,全放入一个字符指针数组中,函数便会遍历这个数组的每个元素。
运行结果:
5.6调用execle函数的案例:
getenv函数可以用于获取环境变量中指定属性的参数!
pro_rep2.c文件:
pro_rep2.c文件:
注:这是不加environ时,使用execle函数替换新程序的结果!
5.6.2execle案例2:
在该函数中获取环境变量中不存在的一个属性MYENV,得到的结果如下:
解决方法:
采用putenv去给环境变量添加一个属性MUENV,给其赋值,这样myenv.c代码中的MYENV就不再是full了。
运行结果:
execl系列函数总结:
事实上,只有execve是真正的系统调用函数,另外其它五个函数:execl,execv,execvp,execle,execlp的底层代码最终都是复用 execve构成,说白了就是这五个函数都是将execve作为底层封装后形成的系统接口函数,因为我们在实践过程中总需要这种封装型的特殊函数。