目录

Linux进程程序替换

Linux:进程程序替换


https://i-blog.csdnimg.cn/direct/5bc90871217c402b93ef885bab670dfe.png

前言

一般情况下,对应的语言写的程序只能调用对应的语言的接口,对于其它类型的语言的接口,如C++就不能 调用java或者python的接口,那如果我们想要调用别人的接口怎么办?所以进程程序替换就能很好的解决这个问题,这样我们就可以使用现成其他语言接口的程序而不用费力地去再实现一个,很大程度上能减少我们的编程成本。


一 进程程序替换的概念

进程程序替换按照字面意思理解就是 使用一个新的程序来替换掉原来的程序,此时进程将执行新程序的代码,而不再执行原有程序的代码 。一般情况下,我们都是在父进程中fork一个子进程出来,让子进程去执行新程序的代码。


二 进程程序替换的原理

用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种exec函数 以执行另一个程序。当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动开始执行。调用exec并不创建新进程,所以调用exec前后该进程的id并未改变。

https://i-blog.csdnimg.cn/direct/a34feb0874804ac2aa2e844f35757a69.png

假设一开始父子进程都执行的是可执行程序A,如果后面想要执行B程序,那么此时就需要进行进程程序替换,发生替换的时候,操作系统会调用系统调用接口,首先操作系统会将父进程的代码和数据拷贝一份,然后将B程序加载到内存中,重新建立子进程的页表,更新子进程的页表中的映射关系,但是这里修改的只是子进程页表中的物理地址那一栏,此时的父子进程的虚拟地址那一栏还是一样的,只不过操作系统修改了虚拟地址和物理地址之间的映射关系,从而实现了父子进程的代码分离。此时父子进程是互不干扰的。

进程替换后:

https://i-blog.csdnimg.cn/direct/4363f34c20a544b087a7ee5226a0b845.png

所以进程程序替换就是想让子进程去执行其他磁盘的文件, 其本质还是将指定程序的代码和数据加载到指定位置(即覆盖掉原来的自己的代码和数据。)


三 为什么需要进行进程程序替换

我们在Linux下进行系统编程的时候,我们在创建子进程后,一般是让它执行父进程的代码片段或者让子进程单独去执行磁盘中的其他程序,其他程序可能设计:C/C++、java等不同语言的程序。想让子进程处理后面的事的时候就需要进行进程程序替换。


四 如何进行进程程序替换

进程程序替换是由操作系统通过 调用系统接口 来实现的,但是我们需要提前明确一个点,就是使用这些调用接口的注意事项,我们在替换程序的时候,肯定需要知道这个程序在哪里吧,在找到这个程序之后,我们还需要知道如何去执行这个程序,就像shell命令行中输入命令一样,在命令后面可能还带有许多选项。

1. 进程替换函数

操作系统中有着六种以exec开头的函数,统称exec函数

https://i-blog.csdnimg.cn/direct/b52d626b48c74bbeb3919514c53eaf93.png

  • 这些函数如果调用成功则加载新的程序从 启动代码开始执行 。如果调用出错则返回-1;所以说exec()函数只有出错的返回值而没有成功的返回值。
  • 可以看到这些函数名字都有着很多的共同点:同样都是exec开始的,后面跟不同的字母以表示不同的功能。
  • 再看他们的参数,我们都可以大概猜出来一二,比如path表示替换的程序路径,arg我们在环境变量一篇中见过,表示如何执行该命令,就像shell命令行里命令后跟的选项一样,如 ls -a的“-a”。“…”表示的是可变参数,file表示的是替换程序的文件名,envp表示的是环境变量
  • l(list):表示参数采用列表,
  • v(vector):表示参数使用数组
  • p(path):自动搜索环境变量PATH
  • e(env):表示自己维护环境变量

➊ execl()函数

我们fork()出一个子进程让它去执行shell命令中的ls命令,并且带上选项参数。我们使用execl函数,传的参数是ls命令的路径和可执行命令及选项参数,并且要以NULL结尾(告诉函数传参结束)

https://i-blog.csdnimg.cn/direct/99be27fd9f2546d4a7f2f5257bf4e14c.png

运行结果

https://i-blog.csdnimg.cn/direct/abadcfb2e4a742c9b24c0f9216620b84.png

✍为什么printf(“proces  running done”) 没有打印呢

printf(“proces  running done”)是在execl之后,execl执行完毕的时候,代码已经完全覆盖,开始执行新的代码的程序,所以printf就无法执行了。

前面也说了这些函数如果调用成功则加载新的程序从 启动代码开始执行 。如果调用出错则返回-1;所以说exec()函数只有出错的返回值而没有成功的返回值。 那为什么调用成功了没有返回值呢,因为调用成功之后,程序替换之后,不再执行后续代码,所以替换成功之后返回值已经没意义了。

➋execv()函数

该函数和execl相比较就差在参数存放的方式,后者是以列表方式存放,而该函数是以数组方式存放(vector)

https://i-blog.csdnimg.cn/direct/b69c47381c554c80858deb286b39ed7b.png

https://i-blog.csdnimg.cn/direct/db0500747a1e465997f76377019767fd.png

➌ ()函数

该函数和execl()相比就是多了个P,这里的P指的就是PATH,指的是系统直接到环境变量PATH中去寻找对应的程序,所以在第一个参数中我们并不需要传路径。

https://i-blog.csdnimg.cn/direct/dfa30151f11741d4b7f1cca383e2c1fa.png

https://i-blog.csdnimg.cn/direct/e9031a2b45ed481ab28b012561209187.png

✉上述操作,我们都是执行系统命令,如果我们想替换自己写的程序,该如何做。

https://i-blog.csdnimg.cn/direct/5053223470ab4a7e8c86ab2b9aba9c4f.png

https://i-blog.csdnimg.cn/direct/c0660f94d90441659f7891a34aaf021e.pnghttps://i-blog.csdnimg.cn/direct/5ee9506ca8444d43b9e6c7c52574cae8.pngmakefile文件(多文件下)

https://i-blog.csdnimg.cn/direct/972c209e6ff2470c82e04126f5a0257a.png

运行结果

https://i-blog.csdnimg.cn/direct/648579691b7d494da9f48c13fce5b5fb.png

程序替换:可以使用程序替换,调用任何后端语言对于的任意可执行

➍execle()函数

这个函数与前几个函数相比多了一个e,那么这个e是什么呢?e表示的是环境变量,这个接口的作用是可以给想要执行的程序传入自己定义的环境变量。第三个参数可以覆盖式的将想要传的环境变量传给想要执行的程序,一般两种情况,第一种就是直接传入自定义的环境变量,第二种就是通过environ,并将自定义的环境导出为系统的环境变量

https://i-blog.csdnimg.cn/direct/45f9e897169042c09009a5aaca4bcac4.png

https://i-blog.csdnimg.cn/direct/5d0a5709a8594bfba9a6d5d23acc6c67.png

https://i-blog.csdnimg.cn/direct/0b6fb2af258e4d90a04cbe951b07f272.png

  • 值得注意的是,execle()函数在传入时,这个环境是覆盖式地传入,并不是新增式地传入,这也就是为什么我们打印出来地只有自己定义的环境变量,因为它们把之前的环境变量都覆盖掉了。

https://i-blog.csdnimg.cn/direct/cf34e45a85364fd6b86b7b6b14c58460.png

https://i-blog.csdnimg.cn/direct/832640f86ef44531be7ec83b3abea3b8.png

如何不覆盖式地添加新的环境变量传给子进程呢?

https://i-blog.csdnimg.cn/direct/61e893c57ce044278f0fcc586aad6de3.png

https://i-blog.csdnimg.cn/direct/372bd93def984bf181f24ecdedacd88c.png

➎execve()*

int execve(const char *filename, char *const argv[], char *const envp[]);

上面的函数本质上都调用了该函数,所以是该函数的封装,是系统真正的调用接口。