(1)exec函数族说明
fork 函数是用于创建一个子进程,该子进程几乎拷贝了父进程的全部内容,但是,这个
新创建的进程如何执行呢?这个exec 函数族就提供了一个在进程中启动另一个程序执行的
方法。它可以根据指定的文件名或目录名找到可执行文件,并用它来取代原调用进程的数据
段、代码段和堆栈段,在执行完之后,原调用进程的内容除了进程号外,其他全部被新的进
新创建的进程如何执行呢?这个exec 函数族就提供了一个在进程中启动另一个程序执行的
方法。它可以根据指定的文件名或目录名找到可执行文件,并用它来取代原调用进程的数据
段、代码段和堆栈段,在执行完之后,原调用进程的内容除了进程号外,其他全部被新的进
程替换了。另外,这里的可执行文件既可以是二进制文件,也可以是Linux 下任何可执行的
脚本文件。
在 Linux 中使用exec函数族主要有两种情况:
· 当进程认为自己不能再为系统和用户做出任何贡献时,就可以调用任何exec 函数族
让自己重生;
· 如果一个进程想执行另一个程序,那么它就可以调用fork 函数新建一个进程,然后
调用任何一个exec,这样看起来就好像通过执行应用程序而产生了一个新进程。(这种情况
非常普遍)
脚本文件。
在 Linux 中使用exec函数族主要有两种情况:
· 当进程认为自己不能再为系统和用户做出任何贡献时,就可以调用任何exec 函数族
让自己重生;
· 如果一个进程想执行另一个程序,那么它就可以调用fork 函数新建一个进程,然后
调用任何一个exec,这样看起来就好像通过执行应用程序而产生了一个新进程。(这种情况
非常普遍)
(2)exec函数族语法
实际上,在Linux 中并没有exec()函数,而是有6 个以exec开头的函数族,它们之间语
法有细微差别,在下面会详细讲解。
实际上,在Linux 中并没有exec()函数,而是有6 个以exec开头的函数族,它们之间语
法有细微差别,在下面会详细讲解。
exec 函数族成员函数语法
所需头文件#include <unistd.h>
函数原型 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, char *const argv[])
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, char *const argv[])
函数返回值-1:出错
这 6 个函数在函数名和使用语法的规则上都有细微的区别,下面就可执行文件查找方式、
参数表传递方式及环境变量这几个方面进行比较。
· 查找方式
读者可以注意到,表7.3 中的前4 个函数的查找方式都是完整的文件目录路径,而最后
2个函数(也就是以p结尾的两个函数)可以只给出文件名,系统就会自动从环境变量“$PATH”
所指出的路径中进行查找。
· 参数传递方式
exec函数族的参数传递有两种方式:一种是逐个列举的方式,而另一种则是将所有参数
整体构造指针数组传递。
在这里是以函数名的第5 位字母来区分的,字母为“l”(list)的表示逐个列举的方式,
其语法为char *arg;字母为“v”(vertor)的表示将所有参数整体构造指针数组传递,其语法
为*const argv[]。读者可以观察execl、execle、execlp的语法与execv、execve、execvp的区别。
它们具体的用法在后面的实例讲解中会举例说明。
这里的参数实际上就是用户在使用这个可执行文件时所需的全部命令选项字符串(包括
该可执行程序命令本身)。要注意的是,这些参数必须以NULL 表示结束,如果使用逐个列
举方式,那么要把它强制转化成一个字符指针,否则exec将会把它解释为一个整型参数,如
参数表传递方式及环境变量这几个方面进行比较。
· 查找方式
读者可以注意到,表7.3 中的前4 个函数的查找方式都是完整的文件目录路径,而最后
2个函数(也就是以p结尾的两个函数)可以只给出文件名,系统就会自动从环境变量“$PATH”
所指出的路径中进行查找。
· 参数传递方式
exec函数族的参数传递有两种方式:一种是逐个列举的方式,而另一种则是将所有参数
整体构造指针数组传递。
在这里是以函数名的第5 位字母来区分的,字母为“l”(list)的表示逐个列举的方式,
其语法为char *arg;字母为“v”(vertor)的表示将所有参数整体构造指针数组传递,其语法
为*const argv[]。读者可以观察execl、execle、execlp的语法与execv、execve、execvp的区别。
它们具体的用法在后面的实例讲解中会举例说明。
这里的参数实际上就是用户在使用这个可执行文件时所需的全部命令选项字符串(包括
该可执行程序命令本身)。要注意的是,这些参数必须以NULL 表示结束,如果使用逐个列
举方式,那么要把它强制转化成一个字符指针,否则exec将会把它解释为一个整型参数,如
果一个整型数的长度char *的长度不同,那么exec函数就会报错。
· 环境变量
exec 函数族可以默认系统的环境变量,也可以传入指定的环境变量。这里以“e”
(Enviromen)结尾的两个函数execle、execve 就可以在envp[]中指定当前进程所使用的环境
变量。
· 环境变量
exec 函数族可以默认系统的环境变量,也可以传入指定的环境变量。这里以“e”
(Enviromen)结尾的两个函数execle、execve 就可以在envp[]中指定当前进程所使用的环境
变量。
exec 函数名对应含义
前4位统一为:execl:
前4位统一为:execl:
第5位 l: 参数传递为逐个列举方式execl、execle、execlp
v:参数传递为构造指针数组方式execv、execve、execvp
v:参数传递为构造指针数组方式execv、execve、execvp
第6位 e:可传递新进程环境变量execle、execve
p:可执行文件查找方式为文件名execlp、execvp
p:可执行文件查找方式为文件名execlp、execvp
(3)exec使用实例
下面的第一个示例说明了如何使用文件名的方式来查找可执行文件,同时使用参数列表
的方式。这里用的函数是execlp。
/*execlp.c*/
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main()
{
if(fork()==0){
/*调用execlp函数,这里相当于调用了“ps -ef”命令*/
if(execlp("ps","ps","-ef",NULL)<0)
perror("execlp error!");
}
}
在该程序中,首先使用fork函数新建一个子进程,然后在子进程里使用execlp函数。读
者可以看到,这里的参数列表就是在shell中使用的命令名和选项。并且当使用文件名的方式
进行查找时,系统会在默认的环境变量PATH 中寻找该可执行文件。
下面的第一个示例说明了如何使用文件名的方式来查找可执行文件,同时使用参数列表
的方式。这里用的函数是execlp。
/*execlp.c*/
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main()
{
if(fork()==0){
/*调用execlp函数,这里相当于调用了“ps -ef”命令*/
if(execlp("ps","ps","-ef",NULL)<0)
perror("execlp error!");
}
}
在该程序中,首先使用fork函数新建一个子进程,然后在子进程里使用execlp函数。读
者可以看到,这里的参数列表就是在shell中使用的命令名和选项。并且当使用文件名的方式
进行查找时,系统会在默认的环境变量PATH 中寻找该可执行文件。
接下来的示例2 使用完整的文件目录来查找对应的可执行文件。注意目录必须以“/”开
头,否则将其视为文件名。
/*execl.c*/
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main()
{
if(fork()==0){
/*调用execl函数,注意这里要给出ps程序所在的完整路径*/
if(execl("/bin/ps","ps","-ef",NULL)<0)
perror("execl error!");
}
}
头,否则将其视为文件名。
/*execl.c*/
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main()
{
if(fork()==0){
/*调用execl函数,注意这里要给出ps程序所在的完整路径*/
if(execl("/bin/ps","ps","-ef",NULL)<0)
perror("execl error!");
}
}
示例3 利用函数execle,将环境变量添加到新建的子进程中去,这里的“env”是查看当
前进程环境变量的命令,如下所示:
/*execle*/
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main()
{
/*命令参数列表,必须以NULL结尾*/
char *envp[]={"PATH=/tmp","USER=sunq",NULL};
if(fork()==0){
/*调用execle函数,注意这里也要指出env的完整路径*/
if(execle("/bin/env","env",NULL,envp)<0)
perror("execle error!");
}
}
下载到目标板后的运行结果如下所示:
[root@(none) 1]# ./execle
PATH=/tmp
USER=sunq
前进程环境变量的命令,如下所示:
/*execle*/
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main()
{
/*命令参数列表,必须以NULL结尾*/
char *envp[]={"PATH=/tmp","USER=sunq",NULL};
if(fork()==0){
/*调用execle函数,注意这里也要指出env的完整路径*/
if(execle("/bin/env","env",NULL,envp)<0)
perror("execle error!");
}
}
下载到目标板后的运行结果如下所示:
[root@(none) 1]# ./execle
PATH=/tmp
USER=sunq
最后一个示例使用execve函数,通过构造指针数组的方式来传递参数,注意参数列表一
定要以NULL作为结尾标识符。其代码和运行结果如下所示:
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main()
定要以NULL作为结尾标识符。其代码和运行结果如下所示:
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main()
{
/*命令参数列表,必须以NULL结尾*/
char *arg[]={"env",NULL};
char *envp[]={"PATH=/tmp","USER=sunq",NULL};
if(fork()==0){
if(execve("/bin/env",arg,,envp)<0)
perror("execve error!");
}
}
下载到目标板后的运行结果如下所示:
[root@(none) 1]# ./execve
PATH=/tmp
USER=sunq
(4)exec函数族使用注意点
在使用exec 函数族时,一定要加上错误判断语句。因为exec 很容易执行失败,其中最
常见的原因有:
· 找不到文件或路径,此时errno被设置为ENOENT;
· 数组 argv和envp忘记用NULL结束,此时errno被设置为EFAULT;
· 没有对应可执行文件的运行权限,此时errno被设置为EACCES。
/*命令参数列表,必须以NULL结尾*/
char *arg[]={"env",NULL};
char *envp[]={"PATH=/tmp","USER=sunq",NULL};
if(fork()==0){
if(execve("/bin/env",arg,,envp)<0)
perror("execve error!");
}
}
下载到目标板后的运行结果如下所示:
[root@(none) 1]# ./execve
PATH=/tmp
USER=sunq
(4)exec函数族使用注意点
在使用exec 函数族时,一定要加上错误判断语句。因为exec 很容易执行失败,其中最
常见的原因有:
· 找不到文件或路径,此时errno被设置为ENOENT;
· 数组 argv和envp忘记用NULL结束,此时errno被设置为EFAULT;
· 没有对应可执行文件的运行权限,此时errno被设置为EACCES。