1. exec函数说明
fork()函数通过系统调用创建一个与原来进程(父进程)几乎完全相同的进程(子进程是父进程的副本,它将获得父进程数据空间、堆、栈等资源的副本。
注意:子进程持有的是上述存储空间的“副本”,这意味着父子进程不共享这些存储空间。linux将复制父进程的地址空间内容给子进程,因此,子进程有了独立的地址空间。),也就是这两个进程做完全相同的事。写独立,读共享
在fork后的子进程中使用exec函数族,可以装入和运行其它程序(子进程替换原有进程,和父进程做不同的事)。
exec函数族可以根据指定的文件名或目录名找到可执行文件,并用它来取代原调用进程的数据段、代码段和堆栈段。在执行完后,原调用进程的内容除了进程号外,其它全部被新程序的内容替换了。另外,这里的可执行文件既可以是二进制文件,也可以是Linux下任何可执行脚本文件。
2.在Linux中使用exec函数族情况
主要有以下两种:
- 当进程认为自己不能再为系统和用户做出任何贡献时,就可以调用任何exec函数族让自己重生;
- 如果一个进程想执行另外一个程序,那么它就可以调用fork函数新建一个进程,然后调用任何一个exec函数使子进程重生;
3.为什么需要exec函数
(1)fork子进程是为了执行新程序(fork创建了子进程后,子进程和父进程同时被OS调度执行,因此子进程可以单独的执行一个程序,这个程序宏观上将会和父进程程序同时进行)
(2)可以直接在子进程的if中写入新程序的代码。这样可以,但是不够灵活,因为我们只能把子进程程序的源代码贴过来执行(必须知道源代码,而且源代码太长了也不好控制),譬如说我们希望子进程来执行ls -la 命令就不行了(没有源代码,只有编译好的可执行程序)
(3)使用exec族运行新的可执行程序(exec族函数可以直接把一个编译好的可执行程序直接加载运行)
(4)我们有了exec族函数后,我们典型的父子进程程序是这样的:子进程需要运行的程序被单独编写、单独编译连接成一个可执行程序(比如叫hello),(项目是一个多进程项目)主程序为父进程,fork创建了子进程后在子进程中exec来执行hello,达到父子进程分别做不同程序同时(宏观上)运行的效果。
4.exec族的6个函数介绍
#include <unistd.h>
extern char **environ; //系统环境变量
int execl(const char *path, const char *arg, ...
/* (char *) NULL */);
int execlp(const char *file, const char *arg, ...
/* (char *) NULL */);
int execle(const char *path, const char *arg, ...
/*, (char *) NULL, char * const envp[] */);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[],
char *const envp[]);
(1)execl和execv
这两个函数是最基本的exec,都可以用来执行一个程序,区别是传参的格式不同。execl是把参数列表(本质上是多个字符串,必须以NULL结尾)依次排列而成(l其实就是list的缩写),execv是把参数列表事先放入一个字符串数组中,再把这个字符串数组传给execv函数。(2)execlp和execvp 这两个函数在上面2个基础上加了p,较上面2个来说,区别是:上面2个执行程序时必须指定可执行程序的全路径(如果exec没有找到path这个文件则直接报错),而加了p的传递的可以是file(也可以是path,只不过兼容了file。加了p的这两个函数会首先去找file,如果找到则执行执行,如果没找到则会去环境变量PATH所指定的目录下去找,如果找到则执行如果没找到则报错)
(3)execle和execvpe 这两个函数较基本exec来说加了e,函数的参数列表中也多了一个字符串数组envp形参,e就是environment环境变量的意思,和基本版本的exec的区别就是:执行可执行程序时会多传一个环境变量的字符串数组给待执行的程序。
5.exec函数族使用注意点
在使用exec函数族时,一定要加上错误判断语句。因为exec很容易执行失败,其中最常见的原因有:
- 找不到文件或路径,此时errno被设置为ENOENT。
- 数组argv和envp忘记用NULL结束,此时errno被设置为EFAULT。
- 没有对用可执行文件的运行权限,此时errno被设置为EACCES。
6.execlp函数举例
execlp.c源代码如下:
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
int main(int argc, char *argv[])
{
//以NULL结尾的字符串数组的指针,适合包含v的exec函数参数
char *arg[] = {"ls", "-a", NULL};
/**
* 创建子进程并调用函数execl
* execl 中希望接收以逗号分隔的参数列表,并以NULL指针为结束标志
*/
if( fork() == 0 )
{
// in clild
printf( "1------------execl------------\n" );
if( execl( "/bin/ls", "ls","-a", NULL ) == -1 )
{
perror( "execl error " );
exit(1);
}
}
/**
*创建子进程并调用函数execv
*execv中希望接收一个以NULL结尾的字符串数组的指针
*/
if( fork() == 0 )
{
// in child
printf("2------------execv------------\n");
if( execv( "/bin/ls",arg) < 0)
{
perror("execv error ");
exit(1);
}
}
/**
*创建子进程并调用 execlp
*execlp中
*l希望接收以逗号分隔的参数列表,列表以NULL指针作为结束标志
*p是一个以NULL结尾的字符串数组指针,函数可以DOS的PATH变量查找子程序文件
*/
if( fork() == 0 )
{
// in clhild
printf("3------------execlp------------\n");
if( execlp( "ls", "ls", "-a", NULL ) < 0 )
{
perror( "execlp error " );
exit(1);
}
}
/**
*创建子里程并调用execvp
*v 望接收到一个以NULL结尾的字符串数组的指针
*p 是一个以NULL结尾的字符串数组指针,函数可以DOS的PATH变量查找子程序文件
*/
if( fork() == 0 )
{
printf("4------------execvp------------\n");
if( execvp( "ls", arg ) < 0 )
{
perror( "execvp error " );
exit( 1 );
}
}
/**
*创建子进程并调用execle
*l 希望接收以逗号分隔的参数列表,列表以NULL指针作为结束标志
*e 函数传递指定参数envp,允许改变子进程的环境,无后缀e时,子进程使用当前程序的环境
*/
if( fork() == 0 )
{
printf("5------------execle------------\n");
if( execle("/bin/ls", "ls", "-a", NULL, NULL) == -1 )
{
perror("execle error ");
exit(1);
}
}
/**
*创建子进程并调用execve
* v 希望接收到一个以NULL结尾的字符串数组的指针
* e 函数传递指定参数envp,允许改变子进程的环境,无后缀e时,子进程使用当前程序的环境
*/
if( fork() == 0 )
{
printf("6------------execve-----------\n");
if( execve( "/bin/ls", arg, NULL ) == 0)
{
perror("execve error ");
exit(1);
}
}
return EXIT_SUCCESS;
}
运行结果
~/workspace/codetest$ ./a.out
1------------execl------------
. … a.out assert.c exec.c pid.c useless wait.c
2------------execv------------
. … a.out assert.c exec.c pid.c useless wait.c
3------------execlp------------
. … a.out assert.c exec.c pid.c useless wait.c
4------------execvp------------
. … a.out assert.c exec.c pid.c useless wait.c
5------------execle------------
. … a.out assert.c exec.c pid.c useless wait.c
6------------execve-----------
. … a.out assert.c exec.c pid.c useless wait.c
wait.c
5------------execle------------
. … a.out assert.c exec.c pid.c useless wait.c
6------------execve-----------
. … a.out assert.c exec.c pid.c useless wait.c