文章目录
- 代码现象
- 基本原理
- 多进程版本
- 实例
- 基本原理
- 使用所有的替换方法,并且认识函数的参数含义
- execl
- execv
- execlp、execvp
- execvpe
- 总结
代码现象
#include<stdio.h>
#include<unistd.h> int main()
{ printf("testexec begin! ...\n"); execl("/usr/bin/ls","ls","-l","-a",NULL); printf("testexec end! ...\n"); return 0;
}
程序运行后,调用execl
函数后,我们的程序去执行了ls
命令,原来的进程中printf("testexec end! ...\n");
没有执行。
基本原理
当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。调用exec并不创建新进程,所以调用exec
前后该进程的id并未改变
我们知道,进程=内核数据结构+代码数据
程序替换的本质是将当前进程的代码和数据进行替换。
替换的时候,会不会创建新的进程?
答案是没有!!只不过是拿老程序的壳子执行新程序的代码。
站在被替换进程的角度:本质上是这个程序被加载到内存。使用exec
系列函数加载,exec
系列函数类似一种Linux上的加载函数。
所以为什么上述现象中,原来的进程中printf("testexec end! ...\n");
没有执行的原因是,调用execl
函数后,去执行ls
程序了,原来的代码和数据被替换了。
exec
系列函数执行完毕后,后续的代码不见了,因为被替换了,因此没有机会去执行了。
不用关心exec
系列函数的返回值,只要替换成功,就不会向后面执行;反之,一定是替换失败。
多进程版本
实例
用fork
创建子进程,让子进程自己去替换
代码:
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>int main()
{printf("testexec begin! ...\n");pid_t id=fork(); if(id==0) { sleep(2); //child execl("/usr/bin/ls","ls","-l","-a",NULL); exit(1); } //father int status=0; pid_t rid=waitpid(id,&status,0); if(rid>0) { printf("father wait success,child,exit code:%d\n",WEXITSTATUS(status)); } printf("testexec end! ...\n"); return 0;
}
现象:
基本原理
创建子进程,子进程完成的任务:
- 让子进程执行父进程代码的一部分
- 让子进程执行一个全新的程序
首先父进程和子进程的PCB、虚拟内存构建好后,通过页表映射到物理内存中。可执行程序testexecl
从磁盘中加载到物理内存中。在代码中,子进程执行一个新的程序execl("/usr/bin/ls","ls","-l","-a",NULL);
此时有一个ls
程序需要从磁盘中加载到物理内存中。之前说过,进程具有独立性,即便是父子进程。将ls
加载到物理内存时,需要在数据层面上做写时拷贝,然后把ls
数据加载进去,修改子进程的映射关系,保证子进程和父进程在数据层面上是独立的。但是ls
不仅仅只有数据,还有代码,因此代码也需要发生写时拷贝。虽然代码是可读的,但是在操作系统看来都无所谓。所以重新开辟内存,将ls
代码加载到物理内存,修改子进程的映射关系。至此,只要程序替换成功,彻底将子进程和父进程分开了。
使用所有的替换方法,并且认识函数的参数含义
execl
int execl(const char *path, const char *arg, ...);
execl中,l:list,列表
path
:需要执行的路劲,需要带路劲
后面的参数:在命令行中怎么执行
例如:
execl("/usr/bin/ls","ls","-l","-a",NULL);
execv
execv(const char *path, char *const argv[]);
v(vector) : 参数用数组
if(id==0) { sleep(2); char* const argv[]={"ls","-l","-a","--color",NULL}; //child // execl("/usr/bin/ls","ls","-l","-a",NULL); execv("/usr/bin/ls",argv); exit(1); }
execlp、execvp
execlp(const char *file, const char *arg, ...);
execvp(const char *file, char *const argv[]);
p(path) : 有p自动搜索环境变量PATH,用户可以不传要执行的路劲(但是文件名要传),直接告诉要执行谁即可
if(id==0) { sleep(2); char* const argv[]={"ls","-l","-a","--color",NULL}; //child // execl("/usr/bin/ls","ls","-l","-a",NULL); // execv("/usr/bin/ls",argv); execvp("ls",argv); exit(1); }
execvpe
上面的程序替换,我们替换的都是系统的命令,那么可不可以替换我们自己写的程序呢?
int execvpe(const char *file, char *const argv[],char *const envp[]);
e(env) : 表示自己维护环境变量
用testexec
二进制程序去执行mypragma
二进制程序
mypragma.cc
代码:
if(id==0) { sleep(2); execl("./mypragma","mypragma",NULL); // sleep(2); // char* const argv[]={"ls","-l","-a","--color",NULL}; //child // execl("/usr/bin/ls","ls","-l","-a",NULL); // execv("/usr/bin/ls",argv); // execvp("ls",argv); exit(1); }
此时,我们写的C++程序就被调度了
除了C++语言可以被C语言调度,其他语言也可以被调度,例如python、脚本语言等…
我们知道了这一件事情之后,再谈execvpe函数:
testecel.c
文件部分代码:
if(id==0) { char* const argv[]={(char*)"mypragma",NULL}; char* const envp[]={(char*)"HAHA=111",(char*)"HEHE=222",NULL}; sleep(2); // execl("./mypragma","mypragma",NULL); execvpe("./mypragma",argv,envp); // sleep(2); // char* const argv[]={"ls","-l","-a","--color",NULL}; //child // execl("/usr/bin/ls","ls","-l","-a",NULL); // execv("/usr/bin/ls",argv); // execvp("ls",argv); exit(1); }
mypragma.cc
代码:
#include<iostream> using namespace std; W>int main(int argc,char* argv[],char* env[]) { int i=0; for(;argv[i];i++) { printf("argv[%d]:%s\n",i,argv[i]); } printf("-----------------------------------\n"); for(i=0;env[i];i++) { printf("env[%d]:%s\n",i,env[i]); } printf("-----------------------------------\n"); cout<<"hellp C++,I am a C++ pragma!!"<<endl; cout<<"hellp C++,I am a C++ pragma!!"<<endl; cout<<"hellp C++,I am a C++ pragma!!"<<endl; cout<<"hellp C++,I am a C++ pragma!!"<<endl; cout<<"hellp C++,I am a C++ pragma!!"<<endl; return 0; }
运行结果:
结论:我们平时自己运行的程序,命令行参数和环境变量是父进程给你的,父进程自己有一个环境变量表,创建子进程时把对应的信息传递给子进程,execvpe
直接交给子进程,环境变量就直接给了子进程。
父进程本身就有一批环境变量,从“爷爷进程”来的,即bash
这个传参,如果传的是自定义的环境变量,那么就整体替换所有环境变量
传环境变量有三种情况:
- 用全新的给子进程
- 用老的环境变量给子进程,environ
- 老的环境变量稍作修改,传递给子进程