#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
代码加载到物理内存,修改子进程的映射关系。至此,只要程序替换成功,彻底将子进程和父进程分开了。
int execl(const char *path, const char *arg, ...);
execl中,l:list,列表
path
:需要执行的路劲,需要带路劲
后面的参数:在命令行中怎么执行
例如:
execl("/usr/bin/ls","ls","-l","-a",NULL);
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(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);
}
上面的程序替换,我们替换的都是系统的命令,那么可不可以替换我们自己写的程序呢?
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
这个传参,如果传的是自定义的环境变量,那么就整体替换所有环境变量
传环境变量有三种情况: