前言:
fork函数是用于创建一个子进程,该子进程几乎是父进程的副本,而有时我们希望子进程去执行另外的程序,exec函数族就提供了一个在进程中启动另一个程序执行的方法。它可以根据指定的文件名或目录名找到可执行文件,并用它来取代原调用进程的数据段、代码段和堆栈段,在执行完之后,原调用进程的内容除了进程号外,其他全部被新程序的内容替换了。另外,这里的可执行文件既可以是二进制文件,也可以是Linux下任何可执行脚本文件。
什么情况下会使用exec组函数?
-
当进程认为自己不能再为系统和用户做出任何贡献时,就可以调用任何exec 函数族让自己重生。
-
如果一个进程想执行另一个程序,那么它就可以调用fork函数新建一个进程,然后调用任何一个exec函数使子进程重生。
函数原型以及语法
实际上,Linux中并没有exec函数,而是有6个以exec开头的函数组成exec函数族。
如下:
#include <unistd.h>
int execl(const char *path, const char *arg, ...) 常用
int execlp(const char *path, 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开头,后面跟的不同字母表示不同的涵义:
- l 表示 list,指代的是命令行参数列表。
- p 表示 path,指定搜索文件file时所使用的path变量。
- v 表示vector,指代的是命令行参数数组。
- e 表示 environment,指代的是环境变量数组。
函数参数
-
path 表示要执行的程序路径,可以是绝对路径或是相对路径。
file 表示要执行的程序名称,如果该参数中包含/字符则视为路径名并直接执行,否则则视为单独的文件名,系统将会根据环境变量PATH中设置的路径顺序去搜索指定的文件。 -
argv 表示命令行参数的矢量数组
-
envp 表示带有该参数的exec函数可以在调用时指定一个环境变量数组,其他不带该参数的exec函数则使用调用进程的环境变量。
-
arg 表示程序的第0个参数,也就是程序名本身,相当于argv[0]。
… 表示命令行参数列表,调用相应程序时有多少个命令行参数就需要有多少个输入参数项。
返回值
函数执行成功不会返回,若执行失败则返回-1,失败原因会记录在error中。

事实上,这6个函数中真正的系统调用只有execve函数,其它5个都是库函数,它们最终都会调用execve这个系统调用。
exec函数族使用注意点
在使用exec函数族时,一定要加上错误判断语句。因为exec很容易执行失败,其中最常见的原因有:
-
找不到文件或路径,此时errno被设置为ENOENT。
-
数组argv和envp忘记用NULL结束,此时errno被设置为EFAULT。
-
没有对应可执行文件的运行权限,此时errno被设置为EACCES。
exec后新进程保持原进程以下特征
环境变量(使用了execle、execve函数则不继承环境变量);
进程ID和父进程ID;
实际用户ID和实际组ID;
附加组ID;
进程组ID;
会话ID;
控制终端;
当前工作目录;
根目录;
文件权限屏蔽字;
文件锁;
进程信号屏蔽;
未决信号;
资源限制;
tms_utime、tms_stime、tms_cutime以及tms_ustime值。
对打开文件的处理与每个描述符的exec关闭标志值有关,进程中每个文件描述符有一个exec关闭标志(FD_CLOEXEC),若此标志设置,则在执行exec时关闭该描述符,否则该描述符仍打开。除非特地用fcntl设置了该标志,否则系统的默认操作是在exec后仍保持这种描述符打开,利用这一点可以实现I/O重定向。
示例:
这里重点讲讲execl函数和execlp函数,execv函数
execlp函数:
从PATH 环境变量中查找文件并执行
说明:
execlp()会从PATH 环境变量所指的目录中查找符合参数file的文件名, 找到后便执行该文件, 然后将第二个以后的参数当做该文件的argv[0]、argv[1]……, 最后一个参数必须用空指针(NULL)作结束。
execlp("ls", "ls", "-la", NULL);
int execl(const char *path, const char *arg, …)
函数说明:
execl()其中后缀"l"代表list也就是参数列表的意思,第一参数path字符指针所指向要执行的文件路径, 接下来的参数代表执行该文件时传递的参数列表:argv[0],argv[1]… 最后一个参数须用空指针NULL作结束。
#include <unistd.h>/*** File: execl.c**/
#include <iostream>
using namespace std;
int main()
{
// 执行/bin目录下的ls, 第一参数为程序名ls, 第二个参数为"-al", 第三个参数为"/etc/passwd"
if(execl("/bin/ls", "ls", "-al", NULL) < 0)
{
cout<<"execl error"<<endl;
}
else
{
cout<<"success"<<endl;
}
return 0;
}
$ vim execl.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void)
{
printf("parent process begin\n");
pid_t pid = vfork();
if(pid == -1)
{
perror("vfork fail");
exit(1);
}
else if(pid == 0)
{
printf("child process begin\n");
if(execl("/bin/ls", "ls", "-l", NULL) == -1)
{
perror("execl fail");
_exit(1);
}
}
sleep(1);
printf("parent process end\n");
return 0;
}
$ gcc -o execl.out execl.c
$ ./execl.out
parent process begin
child process begin
总用量 16
-rw-r--r-- 1 jc jc 400 3月 6 00:32 execl.c
-rwxr-xr-x 1 jc jc 8544 3月 6 00:33 execl.out
parent process end
execle函数举例
利用函数execle,将环境变量添加到新建的子进程中去。
execle.c源代码如下:
#include <unistd.h>
#include <stdio.h>
int main()
{
/*命令参数列表,必须以 NULL 结尾*/
char *envp[]={"PATH=/tmp","USER=sun",NULL};
if(fork()==0){
/*调用 execle 函数,注意这里也要指出 env 的完整路径*/
if(execle("/usr/bin/env","env",NULL,envp)<0)
{
perror("execle error!");
return -1 ;
}
}
return 0 ;
}
编译:gcc execle.c –o execle。
执行./execle,执行结果如下:
PATH=/tmp
USER=sun
可见,使用execle和execve可以自己向执行进程传递环境变量,但不会继承Shell进程的环境变量,而其他四个exec函数则继承Shell进程的所有环境变量。
execve(执行文件)
相关函数
fork,execl,execle,execlp,execv,execvp
表头文件
#include<unistd.h>
定义函数
int execve(const char * filename,char * const argv[ ],char * const envp[ ]);
函数说明
execve()用来执行参数filename字符串所代表的文件路径,第二个参数系利用数组指针来传递给执行文件,
argv要传递给程序的完整参数列表,包括argv[0],它一般是执行程序的名字;最后一个参数则为传递给执行文件的新环境变量数组。
返回值
如果执行成功则函数不会返回,执行失败则直接返回-1,失败原因存于errno 中。
错误代码
EACCES
- 欲执行的文件不具有用户可执行的权限。
- 欲执行的文件所属的文件系统是以noexec 方式挂上。
3.欲执行的文件或script翻译器非一般文件。
EPERM
1.进程处于被追踪模式,执行者并不具有root权限,欲执行的文件具有SUID 或SGID 位。
2.欲执行的文件所属的文件系统是以nosuid方式挂上,欲执行的文件具有SUID 或SGID 位元,但执行者并不具有root权限
int main( void )
{
char* args[] = {
"/bin/ls", NULL };
if ( -1 == (execve("/bin/ls", args, NULL)) )
{
perror( "execve" );
exit( EXIT_FAILURE);
}
puts( "shouldn't get here" );
exit( EXIT_SUCCESS );
}
代码来源:请点击!
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
int main(int argc, char *argv[]) {
pid_t pid;
pid = fork();
if(pid == -1) {
perror("fork");
exit(1);
}
if(pid == 0) {
// 子进程执行另外一个程序
#if 0
// ++++++++++++++++++++ execl ++++++++++++++++++++
/*
* int execl(const char *path, const char *arg, ...);
函数描述:加载一个进程,通过路径 + 程序名来加载。
函数参数:
path:程序路径+程序名字
arg:可执行程序的相关参数,使用NULL结尾
返回值:
成功:无返回
失败:-1
*/
// 子进程执行指定目录下的自己编写的程序,该程序参数
printf("this is func +++++++++ execl +++++++++\n");
execl("/home/robin/a.out", "a.out", NULL);
// 如果执行成功没有返回值
perror("execl");
exit(1);
#endif
#if 0
// ++++++++++++++++++++ execlp ++++++++++++++++++++
/*
* int execlp(const char *file, const char *arg, ...);
函数描述:加载一个进程,借助PATH环境变量,该函数需要配合PATH环境变量来使用,
当PATH中所有目录搜索后没有参数1则出错返回
函数参数:可执行程序的相关参数,使用NULL结尾
file:可执行程序的名字
arg:可执行程序的相关参数,使用NULL结尾
返回值:
成功:无返回
失败:-1
*/
printf("this is func +++++++++ execlp +++++++++\n");
execlp("ls", "ls", "-la", NULL);
// 如果执行成功没有返回值
perror("execlp");
exit(1);
#endif
#if 0
// ++++++++++++++++++++ execvp ++++++++++++++++++++
/*
* int execvp(const char *file, char *const argv[]);
用法同 execlp
*/
printf("this is func +++++++++ execvp +++++++++\n");
char* arg[] = {
"ls", "-a", "-l", NULL};
execvp("ls", arg);
// 如果执行成功没有返回值
perror("execvp");
exit(1);
#endif
#if 0
// ++++++++++++++++++++ execle ++++++++++++++++++++
/*
* int execle(const char *path, const char *arg, ..., char *const envp[]);
* 从用户指定的目录中搜索指定的命令
*/
printf("this is func +++++++++ execle +++++++++\n");
char* env[] = {
"/bin", "./", "/home/robin", NULL}; // 自己指定环境变量,对应PATH
execle("/bin/ls", "ls", "-l", "-a", NULL, env);
// 如果执行成功没有返回值
perror("execle");
exit(1);
#endif
#if 0
// ++++++++++++++++++++ execv ++++++++++++++++++++
/*
* int execv(const char *path, char *const argv[]);
* 用法同:execl
*/
printf("this is func +++++++++ execve +++++++++\n");
char* argv[] = {
"a.out", NULL};
execv("/home/robin/a.out", argv);
// 如果执行成功没有返回值
perror("execv");
exit(1);
#endif
#if 1
// ++++++++++++++++++++ execve ++++++++++++++++++++
/*
* int execve(const char *path, char *const argv[], char *const envp[]);
*/
printf("this is func +++++++++ execve +++++++++\n");
char* argv[] = {
"ps", "a", "u", "x", NULL};
char* env[] = {
"/bin", "/home/", "./", NULL};
execve("/bin/ps", argv, env);
// 如果执行成功没有返回值
perror("execve");
exit(1);
#endif
}
else if(pid > 0)
printf("parent process +++++++++++++++++++\n");
printf("*****************************\n");
return 0;
}