1.程序替换
预备工作
上级目录(…)下的fork目录下的makefile文件拷贝到当前目录并且命名为Makefile
把proc1替换为myexec
1.1 现象和原理
先看现象,可以看到执行了main函数第一句代码,接着就执行的是ls -a -l
这时候回想fork的两种用法,创建子进程一种是指向父进程的部分任务,另一种是执行全新的任务,就是调用exec系列函数实现的程序替换,可以理解为是main函数在exec之后的代码都被替换为了另一个进程的代码,堆、栈、数据也被替换掉,其实我们知道进程是内核数据结构(PCB+页表+进程地址空间)+代码/数据,而要替换数据/代码,其实也就是改页表的虚拟地址和物理地址的映射关系
程序替换的本质,把数据和代码加载(拷贝)到内存中,也就是IO,程序要运行,得先加载到内存中,只有OS(软硬件资源的管理者)有权把数据从一个硬件转移到另一个硬件,也就是说OS必须提供相应的系统调用完成加载的过程,C/C++程序,第一个执行的函数不是main函数,任何程序启动时,先执行OS的加载器的代码,扫描程序在什么路径,找到程序后,调用exec,替换数据和代码,在linux系统下,任何程序之前都会先执行加载器(ld)
也可以看到exec系列的函数之后的代码不会被执行,因为代码已经被替换了,执行的是新的代码,exec系列的函数成功运行时没有返回值
接着我们演示一下fork+exec
子进程执行exec系列的代码,父进程执行自己的代码,我们知道子进程创建的时候会拷贝父进程的页表/进程地址空间,在指向exec时写入堆/栈/数据/代码,发生写时拷贝,父子进程分离
创建一个进程,让该进程可以执行完全不同的程序,最经典的引用场景是bash(命令行解释器),bash最核心的指令是fork+exec,把输入的命令,也就是从命令行输的字符串喂给exec,bash以子进程的方式运行所以要执行的命令
1.2 用法
运行任何进程第一件事是把对应的可执行文件从磁盘加载到内存中(提供文件路径及名称),第二件事就是如何运行(比如ls的-a,-l选项等)
exec系列的库函数如下
1.2.1 execl
top -d 1 -n 4表示每隔1s刷新一次,一共刷新4次,用于周期性监控进程
创建子进程替换为top指令
exec系列函数后面的代码都被替换了,如果替换成功,系统指令或者自己写的程序一般运行结束都会主动退出;如果替换失败,执行后续指令(一般exit填入的值表示出现异常)
从man手册可以看到,正常运行结束,exec系列函数是没有返回值的,如果出现异常,返回值是-1,并且设置errno
测试如下,传入错的path
1.2.2 execlp
execlp和execl的区别是,我们看到下图,一共参数是path,一个参数是file,只要带p就是我们不需要传可执行文件所在的路径,只需要传文件名即可,系统会自动去环境变量PATH表明的路径下查找
1.2.3 execv
我们看到execv和execl的区别是在传第二个及以后参数时,execl是列举,execv的v是vector,把参数存到一个向量表(指针数组)构建一个参数表,接着传入该向量char* const pconst修饰的是p,也就是指针本身,指针本身不能被修改,不能执行其它元素,但可以修改*p,也就是p指向的内容
const char* pconst修饰的是*p,也就是p指向的内容不能被修改,但是p本身可以修改,也就是p可以改指向,指向其它元素
char* const argv[]是数组指针,每个元素是char* const argv[i],const修饰的是指针,指针本身不能修改,argv[0]=“ls”,指向内容也不能修改(常量字符串),也就是数组本身不能被修改
但其实"/usr/bin/ls"本身是可执行程序,有main函数,main函数有参数,int main(int argc, char* const argv[]),其实execv(const char* path, char* const argv[]),这个argv就是传给ls内的main函数的,argv[0]就是程序名,argv后面的就是选项
那我们在bash命令行输入指令ls -a -l等,argv是bash帮我们传的,也是用exec系列的替换函数,如果是execv,将ls -a -l等构建一个参数表,传入execv
但此处是硬编码的,我们知道,bash命令行是可以执行很多命令的,我们可以把表放到全局,每次创建子进程,根据情况修改参数表传入execv
1.2.4 execvp
execvp和execv的区别是,多了一个p也就是第一个参数只需要传入可执行文件名,而不需要传入可执行文件所在文件路径
测试
但打印结果不带颜色,其实执行/usr/bin路径下的ls也不带颜色
我们bash命令行使用的ls其实是封装过的
在传参的时候带上颜色配置即可
可以看到.c文件编译得到的myexec可执行文件中创建子进程可以替换为.cpp编译得到的可执行文件,也可以调用系统指令,linux下都是elf(xecutable and Linkable Format)
shell脚本
也可以给test.sh➕️可执行权限,直接运行
脚本语言没有编译链接的概念,解释器是由C/C++编写,在命令行启动,解释器变成进程,把脚本中的命令一行一行读取进行执行
运行python文件
python, shell, php, c/c++运行时都是进程,运行时是进程的,都可以进行exec程序替换,是系统概念(不是语言概念)
运行一个程序,把编译器替换为自己的编译器,把代码读进来,就能直接编译代码,编译器也是命令,也有选项,gcc/g++ -o -c -E vs IDE环境,写好代码,点击,fork创建子进程,exec系列函数替换为编译器,读取并编译代码,所有平台(比如windows)下都有类似的函数;安装软件,给一个图形化界面,软件可以配很多程序,图形化界面底层和shell外壳类似,点击👆一个功能,fork+exec调用该功能,vs2022图形化界面的程序,配置文本编辑器,调试器,编译器(三个命令),打开代码,替换编辑器;编译代码,替换编译器;想使用什么功能,fork+exec
1.2.5 execvpe
看到下面,可以通过路径运行cmd.out,但是子进程的环境变量只有该路径,子进程要调用exit返回都找不到exit在哪里,所以替换失败根本原因不是路径不对,也不是理论不对,而是依赖!把进程的环境变量只改成了当前路径,一旦调用任何系统中的函数就出错,下面就是,调用make调不了;cmd.out文件用到动态库,终端等,环境变量找不到,自然出错
所以,我们在使用exce系列函数,带e的是全新定义环境变量,覆盖原有的环境变量,需要保留父进程环境变量的基础上叠加我们需要的环境变量
而在传入路径的时候可以正常运行,因为此时直接按路径找,不依赖我们设置的envp,直接忽略我们设置的envp;但有意思的是argv和envp还是传给了cmd的main函数作为参数,所以打印的环境变量还是只有当前路径
ctrl+r可以实现搜索
在调用excevpe的时候,使用系统环境变量(environ)无法执行,因为cmd.out不在系统环境变量下,追加cmd.out所在路径到系统环境变量就可以
运行成功,如下所示
那么我们在使用bash的时候,命令行的参数,包括argv甚至env都是bash创建子进程替换为要执行的可执行文件,并把命令行参数和环境变量传递给可执行程序,子进程的系统环境变量也是继承于父进程(共用页表,地址空间,写入时发生写时拷贝)
在bash改的PATH,会作为环境变量参数传递给子进程,子进程执行exec替换为myexec,接着myexec创建子进程,替换执行cmd,环境变量参数传给cmd
在环境变量追加cmd的路径
运行结果
只保留追加后的PATH
环境变量为空也能运行,怪了,cmd.out也不在当前目录下
如果execl函数不传环境变量,用的是继承于父进程的环境变量
l 指传递参数的时候进行罗列
v 指将参数作为vector进行传递
e 指是否自定义环境变量
牛刀小试
- 通过fork和exec系统调用可以产生新进程,下列有关fork和exec系统调用说法正确的是? [多选]
A.fork生成的进程是当前进程的一个相同副本
B.fork系统调用与clone系统调用的工作原理基本相同
C.exec生成的进程是当前进程的一个相同副本
D.exec系统调用与clone系统调用的工作原理基本相同
AB
clone是Linux底层创建进程/线程,fork基于clone实现
- 下面哪些属于,Fork后子进程保留了父进程的什么?[多选]
A.环境变量
B.父进程的文件锁,pending alarms和pending signals
C.当前工作目录
D.进程号
AC
B.信号相关信息各进程独立
- 以下代码最终的打印结果是什么
intmain(){inta=0;a++;execl("/usr/bin/pwd","pwd",NULL);printf("%d\n",a++);exit(1);}A.0
B.1
C.2
D.以上都不对
D
- 以下描述正确的有:
A.execl函数可以直接指定可执行程序文件的名称而不需要路径
B.execle函数可以直接指定可执行程序文件的名称而不需要路径
C.execl函数和execle函数的区别是是否自定义设置环境变量
D.execl函数和execlp函数的区别是是否自定义设置环境变量
C
- 以下描述正确的有:[多选]
A.程序替换成功后,运行完新程序,依然会运行原有的代码
B.程序替换成功后,运行完新程序,则程序直接退出
C.程序替换成功后,原进程退出,创建新的进程运行新程序
D.程序替换成功后,原进程没有退出,使用原进程运行新程序
BD