目录
前言
一、进程程序替换
二、execl
三、多进程版execl
四、exec相关函数
1.execlp
2.execv
3.execvp
五、替换自己写的程序
六、替换其他语言程序
七、execle
前言
之前,我们学习了进程的fork创建,进程的等待,执行的代码都是父进程代码的一部分,如果我们想要子进程执行全新的代码并访问全新的数据,不和父进程共享一份代码,此时就需要用上进程程序替换。
一、进程程序替换
进程程序替换顾名思义,就是将其他程序替换过来继续执行,主要是通过exec* 这类函数来帮助我们替换,直接来学这个函数。
二、execl
下面有六个函数,我们先来学习第一个——execl。他可以帮我们执行另一个程序。用法先不解释,等下直接上案例。
我们可以用系统当中的指令来进行替换,比如ls / pwd / clean等等。直接上代码,其中execl函数的第一个参数为你要替换程序的路径,第二个参数为可执行程序名,后面的参数为可变参数,就相当于命令行参数。该程序替换成了 ls -a -l 命令,空格在代码里用引号隔开,命令行怎么写,参数就怎么传,最后传NULL代表参数传递完毕。
#include<stdio.h>
#include<unistd.h> int main()
{ printf("我是一个进程, pid: %d\n",getpid()); execl("/usr/bin/ls","ls","-a","-l",NULL); printf("替换完成,pid: %d\n",getpid()); return 0;
}
我们执行看看效果,打印除了execl之前的printf语句,也成功替换了程序,变成了 ls -a -l,但是并没有打印替换后的printf。
这是因为程序替换,会将磁盘中被替换的程序的代码和数据覆盖到当前进程代码和数据映射到的物理内存上。原代码和数据被覆盖后,根本就看不到你的任何printf了,全是新程序的代码,因此不会打印后续内容。
包括exec函数的返回值,他只有在失败的时候才有返回值 -1 ,成功后没有也不需要返回值,根本不带理原进程的。
三、多进程版execl
通过fork,让子进程完成程序替换,父进程等待子进程,代码如下。
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
int main()
{pid_t id = fork();if(id == 0){//childprintf("我是一个进程, pid: %d\n",getpid());sleep(1);execl("/usr/bin/ls","ls","-a","-l",NULL);printf("替换完成,pid: %d\n",getpid());}else{//fatherpid_t rid = waitpid(-1,NULL,0);if(rid > 0){printf("等待成功, rid: %d\n",rid);}}return 0;
}
运行结果如图,这里我们可以看到子进程的pid为10400,父进程等待的进程id也为10400,由此得出一个结论:程序替换并不会再生成进程,而是原进程的替换。父子进程不再只是数据结构(PCB、进程地址空间、页表)上的解耦了,连代码与数据也解耦了。
四、exec相关函数
刚刚我们学习了第一个函数 execl ,这里一共有6个函数,看起来还有点难学,实际上并不难。
1.execlp
l为list,p为path。
比如usr/bin在path环境变量中,ls命令就在该文件夹下。
因此我们改写代码使用execlp,可以不用添加/usr/bin/ls,直接ls就可以了。代码如下,其中第一个ls代表去环境变量里面找ls,第二个ls是命令中的一部分。
运行依然可以。
2.execv
v代表vector,不再是可变变量,而是一个char *const argv[],这是一个指针数组,char *const是指针指向不能修改。
代码修改如下
运行没问题。
3.execvp
execlp是list与path,execvp是vector与path。
因此修改如下
至于execle与execvpe我们后面用自己写的程序讲解
五、替换自己写的程序
先写一个简单的打印程序
修改Makefile,因为要构成两个可执行程序
主程序的execl第一个参数“./mytest” ,./代表当前文件夹下。
运行结果如下,成功替换为自己写的程序
六、替换其他语言程序
exec 函数不仅仅可以替换C/C++程序,还可以替换其他语言,如bash脚本语言,python语言等。
比如我们写了一个bash脚本
成功的打印并创建了。
exec*叫做进程的程序替换,不管哪个语言,在系统层面上跑都是进程,系统大于一切,因此替换成其他语言的程序也没有问题。
exec*说是进程替换,其实也是一种进程加载,完成了加载器的功能。
七、execle
exelce最后一个参数传的是环境变量。
经过之前的学习,我们知道环境变量是可以由父进程继承给子进程的,那我们用execl环境替换,并不传递环境变量是否可以继承呢?
根据下图看起来没什么问题。
我们可以用函数 putenv 添加上环境变量,也是可以被继承的。
这里我们可以确定,程序替换,只会替换新进程的代码和数据,环境变量不会被替换!
那么现在,我们可以将父进程的环境变量原封不动的传递给子进程了。
那么我们使用execle,并自己写环境变量,传递过去,看看是什么效果。
发现是覆盖,原来的环境变量没有了。
由此可以得知,环境变量的传递可以原封不动、可以新增、可以覆盖!!!
直到了execle的使用,相信对于大家而言execvpe也不难了,这就不多赘述了。
其实这还有一个函数叫做execve,他是2手册的,我们学习的6个函数都是3号手册,execve是最底层的,这6个函数底层都调用了execve,他们主要是为了满足各种调用场景做出的不同。