Linux系统编程——进程替换
目录
前言
二、进程程序替换的概念
三、进程程序替换的原理
编辑
四、为什么需要进行进程程序替换
五、如何进行进程程序替换
1、进程替换函数:
1)execl()函数
2)execv()函数
3) execlp()函数
4) execvp()函数
5)execle函数
6)excpve()函数
7)execve()*
前言
一般情况下,对应的语言写的程序只能调用对应的语言的接口,对于其它类型的语言的接口,如C++就不能 调用java或者python的接口,那如果我们想要调用别人的接口怎么办?所以进程程序替换就能很好的解决这个问题,这样我们就可以使用现成其他语言接口的程序而不用费力地去再实现一个,很大程度上能减少我们的编程成本。
二、进程程序替换的概念
进程程序替换按照字面意思理解就是使用一个新的程序来替换掉原来的程序,此时进程将执行新程序的代码,而不再执行原有程序的代码。一般情况下,我们都是在父进程中fork一个子进程出来,让子进程去执行新程序的代码。其实不单是子进程,当前进程也可以发生进程替换。
三、进程程序替换的原理
- 进程替换前,fork()创建子进程之后,子进程和父进程共享代码和数据的:
我们之前在父子进程那一篇讲到过,当父子进程都没有修改代码和数据,因此父子进程的代码和数据是指向同一块内容的,是共享的。当有一方进行写入数据时,就会发生写时拷贝。如果想要执行不同的代码,此时就需要进行进程程序替换。
假设一开始父子进程都执行的是可执行程序A,如果后面想要执行B程序,那么此时就需要进行进程程序替换,发生替换的时候,操作系统会调用系统调用接口,首先操作系统会将父进程的代码和数据拷贝一份,然后将B程序加载到内存中,重新建立子进程的页表,更新子进程的页表中的映射关系,但是这里修改的只是子进程页表中的物理地址那一栏,此时的父子进程的虚拟地址那一栏还是一样的,只不过操作系统修改了虚拟地址和物理地址之间的映射关系,从而实现了父子进程的代码分离。此时父子进程是互不干扰的。
进程替换后:
所以进程程序替换就是想让子进程去执行其他磁盘的文件,其本质还是将指定程序的代码和数据加载到指定位置(即覆盖掉原来的自己的代码和数据。)
四、为什么需要进行进程程序替换
我们在Linux下进行系统编程的时候,我们在创建子进程后,一般是让它执行父进程的代码片段或者让子进程单独去执行磁盘中的其他程序,其他程序可能设计:C/C++、java等不同语言的程序。想让子进程处理后面的事的时候就需要进行进程程序替换。
比如我们使用的bash、zsh等shell,其实都是运用了进程替换的方式才调用了非常多的命令,就像我们在进程中刚开始说的一样,我们在命令行中运行的每一个命令的父进程都是bash,即每次都是bash创建一个子进程来代替它执行命令。
五、如何进行进程程序替换
进程程序替换是由操作系统通过调用系统接口来实现的,但是我们需要提前明确一个点,就是使用这些调用接口的注意事项,我们在替换程序的时候,肯定需要知道这个程序在哪里吧,在找到这个程序之后,我们还需要知道如何去执行这个程序,就像shell命令行中输入命令一样,在命令后面可能还带有许多选项。
1、进程替换函数:
操作系统中有着六种以exec开头的函数,统称exec函数
- 这些函数如果调用成功则加载新的程序从启动代码开始执行。如果调用出错则返回-1;所以说exec()函数只有出错的返回值而没有成功的返回值。
- 可以看到这些函数名字都有着很多的共同点:同样都是exec开始的,后面跟不同的字母以表示不同的功能。
- 再看他们的参数,我们都可以大概猜出来一二,比如path表示替换的程序路径,arg我们在环境变量一篇中见过,表示如何执行该命令,就像shell命令行里命令后跟的选项一样,如 ls -a的“-a”。“...”表示的是可变参数,file表示的是替换程序的文件名,envp表示的是环境变量。
- l(list):表示参数采用列表,
- v(vector):表示参数使用数组
- p(path):自动搜索环境变量PATH
- e(env):表示自己维护环境变量
1)execl()函数
我们fork()出一个子进程让它去执行shell命令中的ls命令,并且带上选项参数。我们使用execl函数,传的参数是ls命令的路径和可执行命令及选项参数,并且要以NULL结尾(告诉函数传参结束)
2)execv()函数
该函数和execl相比较就差在参数存放的方式,后者是以列表方式存放,而该函数是以数组方式存放的
需要注意的是该函数使用的是字符指针数组,不是字符串列表,数组中的字符串是常量字符串,即const char类型的,而字符指针数组中存放的又是char类型的是支持修改的,因此这里需要将const char强转为char类型。
3) execlp()函数
该函数和execl()相比就是多了个P,这里的P指的就是PATH,指的是系统直接到环境变量PATH中去寻找对应的程序,所以在第一个参数中我们并不需要传路径。
4) execvp()函数
5)execle函数
这个函数与前几个函数相比多了一个e,那么这个e是什么呢?e表示的是环境变量,这个接口的作用是可以给想要执行的程序传入自己定义的环境变量。第三个参数可以覆盖式的将想要传的环境变量传给想要执行的程序,一般两种情况,第一种就是直接传入自定义的环境变量,第二种就是通过environ,并将自定义的环境导出为系统的环境变量,那么这样就可以了
我们使用了environ
变量来遍历并打印出当前进程的环境变量。environ
是一个指向字符串数组的指针,每个字符串都是一个环境变量,格式为"NAME=VALUE"
。这个数组以一个空指针结尾。
makefile文件(多文件下)
运行结果
但是这里并不验证将enviorn环境变量列表传入了env_test中,因为我们如果执行env_test的话还是会有一样的结果。
下面我们自定义一个环境变量列表,观察实验结果
然后env_test不变,观察实验结果
- 可以证实,这个envp参数的作用是接收从父进程传来的环境变量列表。
- 在第一个程序中,我们没有自定义环境变量,子进程运行的env_test程序的环境变量是从父进程传来的。但这个实验现象不明显。
- 第二个程序中,我们自定义了环境变量,并且利用execle函数传给env_test程序,使用environ打印它就出现了我们所定义的环境变量。
- 所以说我们前面说的在创建子进程时,子进程会继承父进程的环境变量,即父进程的环境变量列表传入了子进程,且我们通过实验看到使用execle()函数进行进程替换时,子进程的环境变量,完全取决于父进程传什么环境列表。
- 其他的不带envp参数的进程替换的接口,默认给替换的进程传入的是被替换进程的环境变量列表。
- 值得注意的是,execle()函数在传入时,这个环境是覆盖式地传入,并不是新增式地传入,这也就是为什么我们第二个实验打印出来地只有自己定义的环境变量,因为它们把之前的环境变量都覆盖掉了。
如何不覆盖式地添加新的环境变量传给子进程呢?
putenv()函数
调用方法:只需要在参数中传入需要在环境变量中添加的环境变量即可。
当子进程地环境变量中多了一个环境变量后,父进程地环境变量中是不是也会多出来一个呢?
不会,子进程的环境变量继承于父进程,修改之后是不影响的。
6)excpve()函数
看这个函数的参数,也没有什么新颖的,跟上面的函数也都是大同小异,所以就不演示了。
7)execve()*
int execve(const char *filename, char *const argv[], char *const envp[]);
上面的六个函数本质上都调用了该函数,所以是该函数的封装,是系统真正的调用接口。