exec函数族
在Linux中,exec是一个函数族,它一共有6个函数,如下:
#include <unistd.h>
int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg, ..., char *const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execve(const char *path, char *const argv[], char *const envp[]);
在这6个函数中,execve函数属于系统调用,而其他的五个函数都是在execve函数基础上经过包装而形成的库函数,他们的关系如下图:
exec函数族中的函数的作用是,在一个正在运行的进程内部根据函数所指定的文件名和路径找到可执行的文件,并将进程执行的程序替换为新程序,新程序从main处开始执行。其中这里的文件必须是二进制文件或Linux下可执行的脚本文件,另外,exec函数族的函数只是用磁盘上的一个新程序替换了当前进程的正文段、数据段、堆和栈,其它的并没有改变。
exec函数的命名
- 首先,这六个函数全部都已exec四个字符开头。
- 函数名中含字符’l’的不会含字符’v’,它们的区别是函数名中含有‘l’的表示列举参数,意思就是如果我们要调用”ls -al”指令,就需要将所有参数通过字符串的形式列举出来,即arg=“ls”,”-a”,”-l”,NULL;函数名中含有‘v’表示参数向量表,意思是我们要把参数写在一个指针数组中,例如上面的“ls -al”,我们可以定义一个指针数组char* const agr[]={“ls”,”-a”,”-l”,NULL,}然后向含有字符’v’的函数名的函数传参;我们需要注意的是不管是列举参数,还是参数向量表,最后一个参数都必须是NULL。
- 还有一个区别是字符’p’,函数名含有字符’p’的函数不需要指定路径(绝对路径和相对路径都可以),exec函数会通过PATH环境变量在指定的各目录中搜寻可执行文件;而不含字符’p’的函数需要指定可执行文件的路径。
- 最后一个区别是字符’e’,函数名的最后一个字符是‘e’的函数可以接收一个指向环境字符串指针数组的指针,而其他的函数则使用调用进程中的environ变量为新程序复制现有进程的环境变量。我们可以写一个测试用例来看看:
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
int main()
{
extern char** environ;
char** ptr;
printf("<----environ list---->\n");
for(ptr=environ;*ptr!=0;ptr++)
{
printf("%s\n",*ptr);
}
return 0;
}
在这里,会将当前shell下的所有环境变量全部打印下来,因为这个进程是shell的子进程,所以它将shell的全部环境变量都继承了下来。
exec函数族测试用例
在谈了exec函数族的函数的作用和他们之间的关系以及命名区别后,下面我们将进行实战,来运用这些函数。
- execl
我们用execl函数在vfork函数创建的子进程中进行程序替换,我们需要指定路径,以及列举出怎样对路径中的文件进行操作的参数列表,例如我们想查看当前目录下的文件参数:
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
int main()
{
extern char** environ;
char** ptr;
pid_t id=vfork();
if(id==0)
{
//child
sleep(1);
printf("Hello child,pid:%d,ppid:%d\n",getpid(),getppid());
execl("/bin/ls","ls","-a","-l",NULL);
exit(1);
}
else
{
printf("Hello father,pid:%d,ppid:%d\n",getpid(),getppid());
printf("<----environ list---->\n");
wait(NULL);
}
return 0;
}
在这里我们要提到的一点是我们如何判断exec函数是否执行成功了呢,我们必须要了解的是exec函数族只是为调用它的子进程分配了新的文本段,数据段,堆和栈,其他的都不改变,所以如果我们调用了exec函数后没有执行当前进程中exec族函数后面的代码,那么证明我们调用成功;如果执行了后面的代码,或者exec函数返回值为-1那么则调用失败。
程序结果如下:
- execv
与上面的execl函数不同的是,我们要把execl中的参数列表转换为一个参数向量表来向execv函数进行传参。
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
int main()
{
extern char** environ;
char* const Arg[]={
"ls",
"-a",
"-l",
NULL,
};
pid_t id=vfork();
if(id==0)
{
//child
sleep(1);
printf("Hello child,pid:%d,ppid:%d\n",getpid(),getppid());
//execl("/bin/ls","ls","-a","-l",NULL);
execv("/bin/ls",Arg);
exit(1);
}
else
{
printf("Hello father,pid:%d,ppid:%d\n",getpid(),getppid());
printf("<----environ list---->\n");
wait(NULL);
}
return 0;
}
需要我们注意的是参数向量表的最后一个参数必须为NULL,否则会引起调用错误。
- execvp
相比较于execv,execvp函数主要是我们不需要列出程序的具体路径,只需要列出文件名,函数会根据环境变量PATH自动的在PATH中指定的目录查找我们列出的可执行文件。所以相比较于上面的代码,我们只需要把
execv("/bin/ls",Arg);
更改成
execvp("ls",Arg);
就OK了。
execlp
同理,execlp函数就是把execl函数的路径更改成可执行文件名。- execle
相比于前面的四个函数,execle函数允许我们指定所替换的进程中的环境变量,而不是所替换的进程直接继承他的父进程的environ,我们来看看下面这个例子:
- execle
//MyEnv.c
#include<stdio.h>
int main()
{
extern char** environ;
char** ptr;
printf("<----env list---->\n");
for(ptr=environ;*ptr;ptr++)
{
printf("%s\n",*ptr);
}
return 0;
}
首先我们不调用execle函数给这个被替换的程序,当我们单独执行它时,它会输出当前shell的所有环境变量的内容。
当我们在一个进程中用execle将Env(上面这个程序生成的可执行文件)替换当前程序时,我们再来看看结果:
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
int main()
{
extern char** environ;
char* const Envp[]={
"MYPATH=/leap",
"MyID=010",
NULL
};
pid_t id=vfork();
if(id==0)
{
//child
sleep(1);
printf("Hello child,pid:%d,ppid:%d\n",getpid(),getppid());
//execl("/bin/ls","ls","-a","-l",NULL);
//execvp("ls",Arg);
execle("./Env","./Env",NULL,Envp);
exit(1);
}
else
{
printf("Hello father,pid:%d,ppid:%d\n",getpid(),getppid());
printf("<----environ list---->\n");
wait(NULL);
}
return 0;
}
我们发现在运行程序时,Env中的environ中的环境变量内容变成我们在test中所指定的环境变量了。