引言:
父进程通过fork()函数创建一个子进程,子进程将和父进程运行相同的代码,但创建子进程的大多情况,是希望能够运行一些其他的程序,这时候就需要用到进程程序替换。
exec函数族
想要实现进程程序替换就要用到exec函数族,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[]);
调用exec函数族内的函数,能够替换当前当前程序,转而运行一段新程序。
exec函数族中的函数的作用是,在一个正在运行的进程内部根据函数所指定的文件名和路径找到可执行的文件,并将进程执行的程序替换为新程序,新程序从main处开始执行。
这里有几点需要注意:
- exec函数族替换掉了当前程序虚拟地址空间内的代码和数据,并不重新创建页表和虚拟地址空间,这也同样说明进程的标识符(pid)不变。
- 当exec函数族的函数调用成功后,会直接运行新代码,原代码exec函数后面的代码将不在运行(因为代码段里的代码已经被换掉了)。除非调用失败。
- 只有exec函数调用失败(调用成功是没有返回值的),才有返回值,失败返回-1.
- 替换后的新程序将从入口函数运行。
- 函数族内只有execve属于系统调用,其他的几个函数都是经过封装的库函数,最终都会调用execve,下图是对各个函数调用的过程
exec函数解析
之前只是写出了exec函数的所有种类,下面将对各个函数进行分析。
exec函数的命名
exec函数族内的函数都是以exec开头的,他们的具体功能取决于最后的一个或多个字母:
最后的字母代表如下含义:
l(list): 表示参数采用列表
v(vector):参数用数组
p(path):能够自动搜索环境变量path
e(env):表示自己维护环境变量
不带字母p(表⽰path)的exec函数, 第⼀个参数必须是程序的相对路径或绝对路径,例如 “/bin/ls“或”./a.out”,⽽不能 是”ls”或”a.out”。
对于带字母p的函数,无需写全路径,如果参数中包含/,则将其视为路径名。否则视为不带路径的程序名,在PATH环境变量的⽬录列表中搜索这个程序。
带有字母l(表⽰list)的exec函数,要求将新程序的每个命令⾏参数都当作⼀个参数传给它,命令⾏ 参数的个数是可变的,因此函数原型中有…,…中的最后⼀个可变参数应该是NULL, 起sentinel的作⽤。
带有字母v(表⽰vector)的函数,则应该先构造⼀个指向各参数的指针数 组,然后将该数组的⾸地址当作参数传给它,数组中的最后⼀个指针也应该是NULL,就像main函数 的argv参数或者环境变量表⼀样。
对于以e(表⽰environment)结尾的exec函数,要自己组装环境变量,其他exec函数仍使⽤当前的环境变量表执⾏新程序。
下面对各个函数进行测试:
1.execl:
因为execl函数没有字母p,所以第一个参数要写完整的路径名,且因‘l’,所以每个命令行参数均要作为execl的参数,且最后以NULL结尾。
#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;
}
运行结果:
2.execv
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;
}
3.execvp
相比较于execv,execvp函数主要是我们不需要列出程序的具体路径,只需要列出文件名,函数会根据环境变量PATH自动的在PATH中指定的目录查找我们列出的可执行文件。
所以只要吧把上述代码中的 execv("/bin/ls",Arg) 改成execvp(“ls”,Arg);即可
4.execlp
同理,execlp函数就是把execl函数的路径更改成可执行文件名。
5.execle
相比于前面的四个函数,execle函数允许我们指定所替换的进程中的环境变量,而不是所替换的进程直接继承他的父进程的environ.
#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;
}