前言: 本节内容进入linux进程控制板块的最后一个知识点——进程替换。 通过本板块的学习, 我们了解了进程的基本控制方法——进程创建, 进程退出, 进程终止, 进程替换。
进程控制章节和上一节进程概念板块都是在谈进程, 也就是说, 进程的相关内容博主整整通过两个大章, 一共12篇文章进行讲解, 并且几乎每一篇的字数都达到了纯论述五千字左右, 少的也有3500字以上, 多的甚至将近破7000。由此可以看到进程非常重要以及学习难度大。 其中进程概念是一个大难点, 里面知识点多, 晦涩难懂, 博主足足讲解了9篇,并且文章很长。 而进程控制有了前面进程概念的基础, 在理解起来就相对轻松一些, 并且内容较少, 论述性文字可能少一些, 博主通过包括本篇在内的3篇文章就可以完成讲解。 总之, 进程很重要, 希望友友们耐心学习。下面, 进入本节的学习吧。
ps:本节适合了解进程概念, 以及进程的创建, 退出, 终止和等待的友友们进行观看。
目录
什么是进程替换
linux下有一种接口可以进行进程替换工作——exec
使用man手册, 然后就可以观察到详细内容:
上面的这些接口, 他们的工作都是一样的——把一个文件(可执行程序)替换程当前进程。
单进程的进程替换
我们先以单进程, 也就是没有创建子进程为例, 来使用一下, 试验一下进程替换函数——exec, 我们这里使用的是execl函数, 因为它是最简单的一个进程替换函数。
execl的参数是可变参数, 最后一个参数一定是NULL。如下图:
运行结果如下:
运行后我们就发现我们自己写的程序竟然能够将系统命令封装起来进行执行——这个过程, 就叫做程序替换。 而程序替换的现象就是把别人的程序替换一下, 在执行替换函数的之前逻辑的代码会跑,但是之后逻辑的代码就不会跑了。
进程替换的原理
程序替换我们从上面的实验就能看到, 其实就是调用一下系统里面的其他程序, 生成一个新的进程。这个新的进程会生成一个PCB。
首先, 会生成如下图:
然后调用ls, 那么就会又将ls的代码数据加载到物理内存, 拷贝一份PCB、虚拟地址空间和页表。 ——这是我们以前对于创建子进程的理解。 但是对于execl, execl执行的程序不会执行上面的操作。而是直接将execl里面的程序的代码替换给物理内存里面原本进程加载的位置。 ——这, 就是程序替换。
多进程的程序替换
然后再看一下多进程的程序替换是什么样子的。 多进程的程序替换, 我们就是要创建一个子进程,然后再执行一次程序替换。 现在我们就来实验一下:
首先, 创建一个父进程:
然后, 根据父进程, 创建, 拷贝出子进程。
然后程序替换子进程:
在没有运行程序前, 什么都不会打印。
开始运行程序后, 父子进程都会被打印。
5秒后子进程被替换, 执行ls。 同时执行完毕被等待, 资源被回收, 我们就能看到只会打印父进程了!!
程序替换有没有创建新的进程
刚开始运行的时候, 父子进程的PID一直没有变。 我们知道, 创建新进程需要创建新的PCB和地址空间以及页表。 然后拷贝所有的数据, 当涉及物理内存的修改时发生写时拷贝。 而父子进程的PID没有变, 也就是说, 程序替换不创建新进程, 只进行进程的代码和数据的替换工作!!
那么, 为什么execl之后的代码没有被执行呢? 因为对于execl之后的代码来说, 这些代码的本质上也是属于原本的进程的。 但是当这个execl被执行, 这个进程的代码和数据就被替换了。 原本的代码和数据当然就没有用了!!
CPU是如何获取程序的入口地址的
我们的CPU是如何获取程序的入口地址的呢? 这是因为, linux中形成的可执行程序, 是有格式的——ELF。 也就是可执行程序的表头, 这个表描述了可执行程序的段, 比如代码段, 数据段等等。 同时也有一个可执行程序的入口地址。
详解exec接口
现在重新看一下这些exec接口
execl
现在看博主写的这个程序:
上面的程序调用了execl。 首先我们思考一下, 执行一个程序的第一件事是什么? 是不是要找到这个程序?所以, execl函数里面的第一个参数就是一个路径,是我们要执行的程序的绝对路径(相对路径也可, 但是执行命令的地方会被限制)。然后找到这个程序后, 我们是不是还要看看执行什么选项? 要以什么方式打开这个程序? 所以, 第二个参数以后的参数, 都是一些选项, 并且这些选项的最后一个必须是NULL, 就相当于一个链表。 链表里面存的是这些选项!!!为了区分表尾, 就必须把最后一个选项弄成NULL。
execlp
这个函数名比execl多了一个p, 这个p就叫做PATH路径。 代表execlp自己会在默认的PATH环境变量中查找。 ——也就是说, 我们传参时不需要传路径。 只需要传执行的文件名就行了!
就比如下图:
而之所以还要加上ls, 是因为PATH中只有路径, 没有文件名。
PATH中的路劲, 加上ls文件名。 就能解决第一个——如何找到这个程序的问题。
execv
现在看一下execv, v就是数组, 第一个参数还是路径加文件名。 大师第二个参数是一个字符串指针数组。
这个数组就是将原本的ls -al变成ls, -a, -l。 这三个字符串分别作为字符串元素让这个指针数组指向他们。
下面是实验代码:
exec加载器
那么, 现在问题来了。 请问ls是程序吗? 是程序, 那么是程序, ls就有main, 是不是也有命令行参数? 有了命令行参数, ls的命令行参数是从哪里来的呢? ——是从execv系统调用, 或者execl, execlp里面的参数里面获得的。 execl系列函数作为系统调用, 会把第二个参数的选项作为命令行参数传给第一个参数指向的可执行性程序。
在linux中, 所有进程都是别人的子进程, 在命令行当中, 所有的进程都是bash子进程。 所以, 所有进程在启动的时候, 都是使用exec系列函数启动执行的。 程序替换的本质就是将要替换的进程的数据和代码加载到内存当中。 所以, exec系列函数起到的就是加载器的效果。
exec执行用户写的程序——含execle讲解
现在我们创建一个程序, 让我们原本的程序去调用这个程序:
ps:注意, 在c++当中的后缀可以是cpp, cxx, cc
利用makefile一次创建两个可执行程序,需要使用PHONY, 如下:
然后就会生成下面两个文件:
现在我们写一个程序调用otherExe.exe
运行后, 我们执行process-7-11.exe其实就是执行了otherExe.exe
也可以调用脚本语言, 我们先创建一个shell脚本:
运行结果如下:
由上面两个例子我们就可以知道:语言和语言之间可以替换, 无论是我们的可执行程序, 还是脚本。 那么为什么能够跨语言调用呢?
其实所有语言运行起来, 本质上都是进程!!!
execl根本不是运行语言, 而是运行的进程, 只要是进程, 那么就可以被调用。 ——也就是说, 所有的语言, 只要最终可以变成进程, 那么最终都可以使用C/C++调用!!!
那么, 现在看一下命令行参数:我们将命令行参数传给myargv:
然后命令行参数就可以被打印出来:
我们也可以打印环境变量:
部分的打印结果如下:
那么, 环境变量是什么时候给进程的呢? 环境变量也是数据, 当我们创建子进程的时候, 黄金变量就已经被子进程继承下去!!!
所以, 我们即便没有传递命令行参数, 也是可以的!!!extern char** environ, 在进程定义的时候已经被父进程初始化了。 只要我们不写, 那么最终就是父进程的环境变量表!!!
也就是说, 只要我们不写, 那么就是一张环境变量表。
所以, 程序替换的时候, 环境变量的信息, 不会被替换。 所有如果我们相隔i紫禁城传递环境变量, 能怎么传递?
- 第一种方式是新增环境变量——使用putenv在父进程中创建环境变量让子进程继承。
- 第二种方式是彻底替换环境变量——使用execle替换
putenv
我们先创建一个环境变量
运行后, 我们的程序也有了这个环境变量:
putenv的使用方法:putenv(“环境变量=数值") 在当前进程下创建一个环境变量并继承下去。
在当前进程创建环境变量, 不会影响bash进程。
execle
传环境变量:带e的execl
execle的参数如下:
运行后就是下图: 可以看到运行结果里面也有MYNEWENV=666
我们自定义这一张环境变量表
然后打印出来就是如下, 只会打印出我们自己定义的环境变量, 不会打印系统默认的环境变量。
自定义环境变量表, 采用的是覆盖, 而不是追加, 这就是彻底替换环境变量。
以上, 就是本届的全部内容, 下面是博主整理的本节的笔记: