系统调用execve()函数作用
在Linux程序中,通过调用execve(),进程能够以全新程序来替换当前运行的程序。再次过程中,将丢弃旧有程序,进程的栈.数据以及堆段会被新程序所替换。这个 exec 函数族就提供了一个在进程中启动另一个程序执行的方法。
它根据指定的文件名或目录名找到可执行文件,并用它来代替当前进程的执行映像。也就是说,exec调用并没有生成新进程,一个进程一旦调用 exec函数,它本身就“死亡”了,系统把代码段替换成新程序的代码,放弃原有的数据段和堆栈段,并为新程序分配新的数据段与堆栈段,惟一保留的就是进程的 ID。也就是说,对系统而言,还是同一个进程,不过执行的已经是另外一个程序了。
execve()函数原型
#include <unistd.h>
int execve(const char *filename, char *const argv[],
char *const envp[]);
- filename:包含准备载入当前进程空间的新程序的路径名。既可以是绝对路径,又可以是相对路径。
- argv[]:指定了传给新进程的命令行参数,该数组对应于c语言main函数的argv参数数组,格式也相同,argv[0]对应命令名,通常情况下该值与filename中的basename(就是绝对路径的最后一个)相同。
- envp[]:最后一个参数envp指定了新程序的环境列表。参数envp对应于新程序的environ数组。
实例1
下面的每个代码我都放到了github上,欢迎大家fork/star
GeneralSandman
t_execve.c
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<string.h>
int main(int argc,char * argv[]){
char * argVec[10];
char * envVec[]={"环境变量1","环境变量2",NULL};
argVec[0]=argv[0];
argVec[1]=argv[1];
argVec[2]="参数1";
execve(argv[1],argVec,envVec);
printf("the progress can't to here\n");
exit(EXIT_SUCCESS);
}
envargs.c
下面的每个代码我都放到了github上,欢迎大家fork/star
GeneralSandman
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
extern char ** environ;
int main(int argc,char * argv[]){
int i=0;
char ** ep;
printf("new progress\n");
printf("参数\n");
for(i=0;i<argc;i++)
printf("\t%s\n",argv[i]);
printf("环境变量:\n");
for(ep=environ;*ep!=NULL;ep++)
printf("\t%s\n",*ep);
printf("new progress over\n");
exit(EXIT_SUCCESS);
return 0;
}
运行结果
程序说明
运行t_execve程序,会调用execve()函数,函数将会调用进程argv[1],也就是调用envargs,并将自己的环境变量,参数传递给他。我们发现,printf(“the progress can’t to here\n”); 永远不会被执行(除非调用execve()失败),因为调用execve(),会转到新的进程,自己会被“杀死”;
实例2
t_execve2.c
下面的每个代码我都放到了github上,欢迎大家fork/star
GeneralSandman
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<string.h>
#include<sys/wait.h>
int main(int argc,char * argv[],char ** environ){
pid_t pid;
printf("the example!!!\n");
switch(fork()){
case -1:
printf("fork error\n");
exit(EXIT_FAILURE);
case 0:
printf("child is running\n");
printf("child pid=%d, child parent pid=%d\n",getpid(),getppid());
printf("child uid=%d, child gid=%d\n",getuid(),getpid());
execve(argv[1],argv,environ);
printf("child can't in here\n");
break;
default:
wait(NULL);
printf("parent is running\n");
break;
}
exit(EXIT_SUCCESS);
}
envargs2.c
下面的每个代码我都放到了github上,欢迎大家fork/star
GeneralSandman
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<string.h>
int main(int argc,char * argv[],char ** environ){
int i;
printf("running after the execve()\n");
printf(" pid=%d, parent pid=%d\n",getpid(),getppid());
printf(" uid=%d, gid=%d\n",getuid(),getpid());
printf("the immage over\n");
exit(0);
}
运行结果
程序说明
运行t_execve2程序,fork()之后,子进程会调用execve()函数,而父进程会等待子进程退出(也就是等待envargs2退出)。我们发现:执行新程序保持了原来进程的进程 ID、父进程ID、实际用户 ID 和实际组 ID。同时还可以看到,当调用新的可执行程序后,原有的子进程的映像被替代,不再被执行。
执行新程序后的进程除了保持原来的进程 ID、父进程 ID、实际用户 ID 和实际组 ID 之外,进程还保持了许多原有的特征,主要有:
- 当前工作目录
- 根目录
- 创建文件时使用的屏蔽字
- 进程信号屏蔽字
- 未决警告
- 和进程相关的使用处理器的时间
- 控制终端
- 文件锁
exec族
有 6 个以 exec 开头的函数族,他们之间的语法有细微的差别,函数原型如下所示:
- int execl(const char *path, const char *arg, …)
- int execv(const char *path, char *const argv[])
- int execle(const char *path, const char *arg, … , char *const envp[])
- int execve(const char *path, char *const argv[], char *const envp[])
- int execlp(const char *file, const char *arg, …)
- int execvp(const char *file, *const argv[])
区别
实际上有关exec是一个函数族,包括execle,execlp,execvp,execv,execl但是他们具体的实现都是调用execve()之上。它们的区别就在于对路径名,参数以及环境变量的指定上。下面分别从这三个方面来区分这几个函数:
- 路径名:带p的表示可以通过环境变量PATH去查找,所以我们可以不用绝对路径,比如execlp和execvp就可以直接用filename。
- 参数:带l的execle()和execlp()以及execl()要求在调用中以字符串形式指定参数。首个参数相当于新程序main中的argv[0],因而通常与filename中的basename相同(就是绝对路径的最后一个)。
- 环境变量:以e结尾的允许我们通过envp为新程序显式的指定环境变量,其中envp必须以NULL结尾。