引语
在Linux中,我们可以通过fork函数创建子进程,然后通过fork的返回值,进行分流,分割开父子进程
那,我们创建子进程的目的是什么呢
是为了让子进程帮我们执行特定的任务
我们知道,子进程会继承父进程的代码和一部分数据,那么这时子进程就可以基于父进程的代码,去完成我们给予的任务
但,子进程也可以指向一个全新的程序代码!这就是进程的程序替换
实例
我们先使用execl(const charpath,const chararg,… )
参数
参数 | 参数说明 |
---|---|
const char *path | 要调用的程序的绝对路径 |
const char *arg | 如何执行程序(命令行操作)最后以NULL结束 |
… | 可变参数 |
关于 ... 可变参数
我们在C语言的printf其实用过
比如我们打印多个数字printf(“%d,%d,%d”,num1,num2,num3);
printf这个函数并不知道我们要传几个参数,所以用…可变参数,代表我们可以传多个参数
代码
那么接下来我们就来实际使用一下execl这个函数
首先我们打印5行“begin…”,打印“我已经是一个进程啦”,然后调用execl函数,然后在打印5行"end…"
这里说明一下,execl函数的参数
参数 | 参数说明 |
---|---|
“/bin/ls” | ls命令的绝对路径 |
“ls”,“-l”,“-a” | 要执行的操作(这就是可变参数) |
PS:注意以NULL表示结尾
运行结果如下
可以看到,最开始的"begin…"和"我已经是一个子进程啦"成功打印了,但是后续的“end…”并没有执行,而是像我们在命令行敲了一个ls -la一样,展示当前目录的所有文件
原理
我们知道代码和数据是存储在磁盘的,当我们执行时,会被加载到内存。
ls是一个程序,同样拥有代码和数据,也同样存储在磁盘中
最开始我们自己编写的"begin…"和“我已经是一个进程啦”成功被编译,但是我们在之后调用了execl函数,其会发生程序替换
正如下图
最开始我们正常跑我们自己的代码,有相应的pcb,进程地址空间,页表,但是调用execl后,通过我们指定的程序,会将指定程序从磁盘加载到内存,然后用程序将原来的代码和数据完全覆盖
PS:进程的程序替换,没有创建新的进程。
进程替换的注意点
小知识点
一 .进程的程序替换,没有创建新的进程。
二.程序替换是整体替换,不能局部替换。
三 .进程的程序替换不会影响其他进程。
我们以父子进程举例
如果子进程的程序替换会影响父进程,那么父进程的内容将不会打印,并且总共有两次ls。
反之,则父进程内容打印,只有一次ls。
运行结果如下
结果是等待5秒,然后父进程进行打印。并且只有一次ls
说明子进程的程序替换并没有影响父进程
原因
因为当子进程进行程序替换时,会进行写时拷贝。使得父进程和子进程拥有不一样的进程地址空间和页表
execl函数会调用失败吗?
比如我们尝试调用“lsssss”
运行结果如下
打印了1次“我是子进程”,2次“我是父进程”,返回值是-1
结论:execl函数如果替换成功,不会有返回值,如果替换失败,一定会有返回
所以只要有返回值,就代表调用失败==>所以我们不需要接收返回值
调用失败,就不会程序替换,会执行原来的代码,所以总共打印2次“我是父进程”
应用
因为如果execl函数调用失败,则一定会有返回值,并且继续执行原来的代码
所以我们可以再execl函数后直接调用exit函数,然后让父进程接收返回值,以此就可以判断是否成功程序替换
运行结果如下
父进程成功获取到了子进程exit(1)的退出码。以此判断execl是否调用成功
程序替换函数
以上,我们简单的见识了一下如何程序替换
接下来我们要较为详细的学习所有的程序替换函数
execl
execl(const char *path,const char *arg,...)
执行一个程序,首先第一步:我们要找到他,第二步:我们要确定如何执行他。
这就对应了execl的两个参数
execv
execv(const char *path,char * const argv[] )
这处讲解一下以上两个函数的命名规律
execl的如何执行是一个一个传参,像list一样
execv的如何执行是传一个字符指针数组,像vector那样
所以execv的用法如下
我们需要创建一个字符指针数组,其内容同样是如何执行。然后传入execv的参数
运行结果如下
execlp
execlp(const char *file,const char *arg,...)
execlp的命名和execl就差一个p,这个p的意思就是,PATH。
调用这个函数,他只会在PATH内部,找是否有我们指定的程序
不需要写绝对路径,直接是程序的名称就好
如何执行同execl一样,需要一个一个传
execvp
execvp(const char*file,char *const argv[] )
同execl和execlp的差别
execvp就是不需要传绝对路径的execv
execle
execle(const char*path,char *arg,char *const envp[] )
前两个参数同execl,最后一个参数是传给调用的程序的环境变量表
接下来,我们编写一个C++程序otherproc,然后在myproc里面通过execle调用
在这里,我们证明了之前的一个小知识点:程序替换没有创建新进程
。因为otherproc显示的pid和myproc的pid相同
然后我们再改写一下otherproc.cpp
我们同时获取一下MYENV和PATH两个环境变量
运行结果如下
我们发现原本本应该有的PATH变成了NULL
这是怎么回事呢?
原来程序替换传的环境变量表是覆盖式写入,会将原本的环境变量表完全替代为传入的环境变量表
如果我们要将原本进程的环境变量表传过去呢?
可以用C语言定义的一个常量char**environ
environ是库里的,使用时需加extern表明是外部定义的量
运行结果如下
那么如何在代码中,导入新的环境变量呢?
我们可以使用putenv()
可以将新的环境变量导入环境变量表
运行结果如下
execvpe
execvpe(const char* file,char* const argv[],char *const envp[] )
基于我们以上的学习,很容易理解这些参数
file是程序名,argv[]是如何执行的字符指针数组,envp[]是传递的环境变量表
execve
execve(const char*filename,char *const argv[],char *const envp[] )
filename是程序名,argv[]是如何执行的字符指针数组,envp[]是传递的环境变量表
以上的6个是在3号man手册的,但是execve是单独在2号man手册的。这是为什么呢?
其实,只有execve是操作系统最开始使用的程序替换的函数,之前讲述的6种是对execve进行了封装,使其在不同场景有不一样的效率和适应性。
应用
进程的程序替换不仅限于C程序和C++程序的替换,程序替换是操作系统层面的,对于任何语言和程序都是一样的。
比如我们写一个shell脚本和python的代码
shell脚本
python代码
然后我们在myproc.c中调用这两个程序
结束语
进程的程序替换的讲解基本就结束了
如果感到有收获的话不妨支持一下博主
文章如果有不对或者不足的地方,欢迎大佬们指正,补充。感谢大家的阅读,如果感觉博主写的还可以,麻烦点个赞支持一下,阿里嘎多。拜托了,这对我真的很重要~