Linux进程程序替换

我们之前创建的子进程, 虽然能够与父进程分工干不同的事, 但是其代码还是与父进程是同一份的, 而如果我们希望子进程和父进程跑完全不一样的代码, 我们就需要使用程序替换这个技术

替换原理

##execl()

在这里插入图片描述
我们可以看到execl的参数是可变的

其中
__path是代表你要执行的程序
__arg是代表你想怎么执行

在这里插入图片描述
注意不论你要传递多少个参数, 最后一定得以NULL结尾

在这里插入图片描述
我们运行一下这份代码

在这里插入图片描述
发现我们的代码把ls - a -l这段命令跑起来了
另外我们还发现, execl后的after没有跑起来

这是因为执行execl后, 他直接用ls的代码替换原来进程的代码
用ls的数据替换原本的数据

在这里插入图片描述
在这张图中, 原本进程的绿圈里的东西不变, 页表也只是做微小改动(因为两进程代码和数据所占的内存可能不一样)

用新的程序的代码和数据, 替换自己的代码和数据, 然后从新程序的main函数处开始执行, 这种语法特性, 我们叫做 程序替换

在这里插入图片描述
我们用这份代码来验证一下execl在多进程中的表现

在这里插入图片描述
可以看到在子进程中正常执行了ls, 然后再父进程中也正常执行了自己的代码, 这是为什么呢? 父子进程不是共享一份代码吗?

其实是因为当ls的代码被拷贝的时候发生了写实拷贝
这里其实也说明了, 代码其实也不一定是不可被写入的, 比如刚刚就说了, execl会用新进程的代码替换掉当前的代码

因为子进程即便进行程序替换也会发生写实拷贝, 所以是不会影响到父进程的运行的, 这样印证了, 进程之间是具有独立性的

程序替换有没有创建新进程?

没有!
从哪里可以看出来呢?
1 刚开始运行的时候, 我们运行了原本进程的代码
在这里插入图片描述
2 进行替换后, 进程的pid等信息是没变的

所以execl只进行替换工作, 所以进程所对应的内核数据结构是没有被释放, 没有被重新建立的, 只是里面的一些内容可能会被修改, 但是整个进程是没有没重新创建的, 而是在原本进程基础之上将代码和数据进行了替换。

补充小知识

1 现象: 程序替换成功之后, execl 后续的代码不会被执行, 但是如果替换失败了呢(比如命令不存在, 路径写错了之类的)?
失败了才会继续执行接下来的程序
因此 execl 只有失败返回值 没有 成功返回值

2 我们的cpu怎么知道main函数的入口在哪里?

Linux中形成的可执行程序时有格式的(ELF)这种格式, 在最开头会有可执行程序的表头, 可执行程序的入口地址就在表中! 所以 当我们替换了一个新的可执行程序, 这个可执行程序里也有这个表头, 这个表头里也有入口地址, 所以cpu就知道该从哪里执行新程序了

程序替换的接口

之前算是一个小演示, 现在我们正式的学习程序替换的接口。
我们通过查看man手册, 发现关于替换的接口一共有
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

我们只学习5个接口, 为什么呢? 因为我们学习完这5个接口后, 找到了规律, 其他的两个接口自然就会了。

execl

我们发现所有的接口开头4个字母都是exec所以我们不管这4位。

先学习第一种l 我们可以把这个l理解为list 在用这个函数时, 我们的参数从第二个起是一个一个传的,然后最后一个参数一定得是NULL, 就像链表的最后一位得是, 如图
在这里插入图片描述
所以名字中带l的表示他的参数是可变参数, 传参时是一个一个的传, 且最后一位一定得是NULL

那么我们传的这一个一个的到底是什么玩意?

在这里插入图片描述

其实从第二个参数开始, 我们写命令时怎么写的, 就一个一个的传上去, 只不过把最后一位的enter 改为NULL

我们要执行一个程序的第一件事情是什么? 当然是找到这个程序

所以第一个参数就必须得是以绝对路径或相对路径的形式找到该程序, 后面的这些决定的是接下来该怎么去执行这个程序,比如要涵盖哪些选项
我们之前也了解了main函数的命令行参数argv 就是这个argc实现了程序带参数执行, 我们系统的各种命令可以带参数也都是靠的他, 其实, 第一个参数之后的参数都是传给了我们要执行的程序的argv里

execlp

我么发现这个函数不仅带了l还带了p

这个p指的是 PATH 即execlp自己会在默认的PATH环境变量中查找
在这里插入图片描述

所以我们在使用系统命令等存在了环境变量中的程序时, 用这个函数就可以不带路径。

当然如果你愿意带路径, 这个函数也是可以执行的
在这里插入图片描述
这里就有个问题了, 为什么我们还要去在地址的地方去传一个ls呢?
还是那句话, 我们第一个参数是表明我们要执行什么程序, 后面的参数表明我们该怎么执行。

execv

v表示vector
在这里插入图片描述
他的第一个参数与execl完全一样, 唯一不一样的是第二个参数
其实他们本质上还是一样的, l是以可变参数的形式将如何执行传进去
v是以数组的形式将如何执行传进去, 也就是说其实这个数组里的参数都是传给了ls中main函数的argv里
在这里插入图片描述

示例
在这里插入图片描述

补充小知识

在Linux中 所有的进程都是别人的子进程, 在命令行当中, 所有的进程都是bash的子进程, 所以所有的进程在启动的时候都是靠exec系列的函数来进行启动执行的。

程序替换在单进程中, 其实是将代码和数据加载到内存当中, 然后为进程开辟内存空间所以exec系列函数承担的是一个加载器的效果

execvp

他与execv的区别于 execlp 和 execl的区别一样, 就是在执行PATH中保存的地址中的程序时, 不需要带绝对路径, 直接告诉他要执行那个就行。
在这里插入图片描述

execle

e代表的是env 也就是环境变量, 让我们在执行可执行程序时, 可以传递自己的环境变量

在谈这个函数之前, 我们先谈论一下另外一个话题;


如果我们exec系列的接口能够执行系统命令, 能不能执行我们自己的命令呢?
答案是:肯定可以的。

我们现在就来示例, 用c代码去调用c++ 的可执行程序

我们在编写的时候, 发现makefile似乎不能同时生成两个可执行程序
在这里插入图片描述
如这样就只会生成上面那个可执行程序, 为什么呢?

因为makefile在自顶向下扫描的时候, 完成了一个文件的链式关系后就会直接返回, 所以为了能同时生成两个可执行程序, 我们可以定一个伪目标, 让这个伪目标的依赖关系是我们需要的这两个文件就好
如图:
在这里插入图片描述
解决完了makefile的问题, 我们就回到主线

在这里插入图片描述
我们用这个函数来调用我们的C++代码

在这里插入图片描述
发现是可以正常调用的。

同理, 我们也可以用c, c++ 来调用一些脚本语言, 如python, PHP等

在这里插入图片描述
比如我们用这段来调用我们的bash脚本
在这里插入图片描述
调用成功

无论是我们的可执行程序, 还是脚本, 为什么能跨语言调用呢?

因为所有的语言运行起来本质上都是进程, 只要是进程, 就是可以被调用的。


讲完了这些, 我们就可以验证用execle给被调用的进程传环境变量和命令行参数了

在这里插入图片描述

我们用这段代码来查看, mycommand传给main 的命令行参数

在这里插入图片描述

可以看到我,我们传过去的-a等选项都成功传过去了。

命令行参数我们成功验证了, 接下来我们要验证的就是传环境变量
在这里插入图片描述
我们用这串代码来验证一下环境变量是否被传入

在这里插入图片描述
先不传入环境变量, 直接编译运行, 发现不仅看到了传进来的命令行参数, 还看到了一大堆的环境变量, 这是怎么回事呢?

其实我们前面也讲了, 子进程是会继承父进程的环境变量的, 所以环境变量具有全局性, 该进程是bash的子进程们自然就会有bash的全局变量拉。

子进程是在什么时候拿到父进程的环境变量的?

环境变量也是数据, 当我们创建子进程的时候, 环境变量就已经被子进程继承了。
我们之前讲过, 程序会自带一个全局变量char** environ, 当我们父进程在创建的时候就会初始化这个表, 后面的子进程只要不进行修改, 那么大家就都指向的是同一份数据, 那么这些环境变量就被继承下去了

如何给被调用的进程传环境变量

1 使用puenv 给父进程加入新的环境变量, 环境变量具有全局性, 所以自进程自然就获得了父进程的变量。

2 使用带eexec系列函数, 来调用新进程

在这里插入图片描述

我们使用这段代码来调用新进程

在这里插入图片描述

可以看到环境变量被完全替换为了我们刚刚设置的环境变量

所以我们也就得到了一个结论: 带eexec系列函数, 传入环境变量所采用的策略是覆盖而不是追加

各个接口之间的联系

在这里插入图片描述

图左边的6个函数, 是c语言封装好的库函数, 他们之间唯一的区别就是传参的不同。

而图左的这个函数, 是一个系统调用接口, 图右边的6个库函数其实只是对右边的系统调用的封装

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值