学科分类
目录
Linux编程

exec函数族

使用fork()函数创建的子进程,其中包含的程序代码完全相同,只是能根据fork()函数的返回值,执行不同的代码分支。当每个分支的内容较多时,代码自身便较为庞大;另外,若要执行的分支与程序其它内容并不相干,对子进程来说,除与之对应的分支外的大多内容都是没有意义的。但fork()函数每创建一个子进程,都要将这“庞大”程序或无意义的代码复制一次,如此,代码冗余造成的空间消耗将不可忽视。

高级编程语言中提出了函数的概念,使用函数,可以提高代码是用来,优化代码结构。进程控制中也有类似的功能:若要使进程执行另外一段程序,可以通过调用exec函数族来实现。

exec函数族的功能是:根据指定的文件名或路径,找到可执行文件,用该文件取代调用该函数的进程中的程序,再从该文件的main()函数开始,执行文件的内容。exec函数族可以视为系统调用,共由6个函数组成。

调用exec函数族时不创建新进程,因此进程的pid并未改变。exec只是用新程序中的数据替换了进程中的代码段、数据段和堆、栈中的数据。exec调用成功时没有返回值。

exec函数族一般与fork()函数一起使用:使用fork()函数创建进程,使用exec函数族修该进程任务。调用这两个函数后,进程与其中数据的变化情况如图1所示。

图1 fork()函数与exec

exec函数族中包含6个函数,分别为:execl()、execlp()、execle()、execv()、execvp()、execve(),它们包含在系统库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个函数的函数名非常相似,但函数名与函数的参数列表之间也是有联系的,下面对函数的函数名与其中的参数进行分析。

(1)函数的第一个参数为file或path,它们的区别是:当参数为path时,传入的数据为路径名;当参数为file时,传入的数据为可执行文件名。使用*file作为参数时,若传入的数据中包含“/”,就将其视为路径名;否则系统会根据进程的PATH环境变量,在它指定的各个目录中搜索传入的可执行文件。

(2)读者可以将exec函数族分为以execl与execv开头的两类:函数名中的l通过列举传递参数,参数列表会将执行第一个文件用到的参数逐一列举,由于参数列表长度不定,最后要用哨兵“NULL”表示列举结束;函数名中的v通过参数向量表传递参数,函数中以“char *argv[]”的形式传递文件执行时使用的参数,数组中最后一个参数为哨兵“NULL”。

(3)当以execl和execv为前缀时,若函数名后缀为空,参考分析(1)(2)即可;若函数名中含有p,表示其参数列表的第一个参数接收的为路径;若函数名中含有e,表示environment,即环境变量,参数列表中对应有参数“char *const envp[]”,该参数用于接收用户传入的环境变量,exec函数会使用接收到的环境变量替代默认的环境变量。

读者可在man手册中查询学习更多关于exec函数族的知识。实际上,只有execve()是真正的系统调用,其它五个函数最终都调用execve(),因此execve()函数在man手册第2节,其它exec函数在man手册第3节。

下面通过案例来展示exec函数族的用法。

案例3:在程序中创建一个子进程,之后使父进程打印自己的pid信息,使子进程通过exec函数族获取系统命令文件,执行ls命令。

案例实现如下:

test_exec.c

 1    #include <stdio.h>
 2    #include <stdlib.h>
 3    #include <unistd.h>
 4    
 5    int main()
 6    {
 7        pid_t pid;
 8        pid=fork();
 9        if(pid==-1)
 10        {   
 11            perror("fork error");
 12            exit(1);
 13        }   
 14        else if(pid>0)
 15        {   
 16            printf("parent process:pid=%d\n",getpid());
 17        }   
 18        else if(pid==0)
 19        {   
 20            printf("child process:pid=%d\n",getpid());
 21            //execl("/bin/ls","-a","-l","test_fork.c",NULL);    //①
 22            //execlp("ls","-a","-l","test_fork.c",NULL);        //②
 23            char *arg[]={"-a","-l","test_fork.c",NULL};            //③
 24            execvp("ls",arg);
 25            perror("error exec\n");
 26            printf("child process:pid=%d\n",getpid());
 27        }   
 28        return 0;
 29    }

在案例3中分别使用execl()(①)、execlp()(②)和execvp()(③)函数对ls命令进行了调用,它们的输出结果相同,如下所示:

parent process:pid=3587
child process:pid=3588
-rw-rw-r--. 1 itheima itheima 375 Oct 18 16:18 test_fork.c

需要注意,exec函数族在调用成功时不会产生返回值,但由于种种原因(调用的文件不存在、环境变量错误等等),调用失败的几率比较大,因此在使用时,最好在调用exec函数后,通过perror()函数打印错误信息,判断exec函数族是否调用成功。

其它三个函数的使用方法与以上三个函数的使用方法大同小异,读者可自行尝试使用。

点击此处
隐藏目录