exec函数详解
(1)exec函数说明
fork函数是⽤于创建⼀个⼦进程,该⼦进程⼏乎是⽗进程的副本,⽽有时我们希望⼦进程去执⾏另外的程序,exec函数族就提供了⼀个在进程中启动另⼀个程序执⾏的⽅法。它可以根据指定的⽂件名或⽬录名到可执⾏⽂件,并⽤它来取代原调⽤进程的数据段、代码段和堆栈段,在执⾏完之后,原调⽤进程的内容除了进程号外,其他全部被新程序的内容替换了。另外,这⾥的可执⾏⽂件既可以是⼆进制⽂件,也可以是Linux下任何可执⾏脚本⽂件。
(2)在Linux中使⽤exec函数族主要有以下两种情况
当进程认为⾃⼰不能再为系统和⽤户做出任何贡献时,就可以调⽤任何exec 函数族让⾃⼰重⽣。
如果⼀个进程想执⾏另⼀个程序,那么它就可以调⽤fork函数新建⼀个进程,然后调⽤任何⼀个exec函数使⼦进程重⽣。
(3)exec函数族语法
实际上,在Linux中并没有exec函数,⽽是有6个以exec开头的函数族,下表列举了exec函数族的6个成员函数的语法。
所需头⽂件#include <unistd.h>
函数说明执⾏⽂件
函数原型int execl(const char *pathname, const char *arg, ...)
int execv(const char *pathname, char *const argv[])
int execle(const char *pathname, const char *arg, ..., char *const envp[]) int execve(const char *pathname, char *const argv[], char *const envp[]) int execlp(const char *filename, const char *arg, ...)
int execvp(const char *filename, char *const argv[])
函数返回值
成功:函数不会返回
出错:返回-1,失败原因记录在error中
这6 个函数在函数名和使⽤语法的规则上都有细微的区别,下⾯就可执⾏⽂件查⽅式、参数表传递
⽅式及环境变量这⼏个⽅⾯进⾏⽐较说明。
①    查⽅式:上表其中前4个函数的查⽅式都是完整的⽂件⽬录路径(pathname),⽽最后2个函数(也就是以p结尾的两个函数)可以只给出⽂件名,系统就会⾃动从环境变量“$PATH”所指出的路径中进⾏查。
前4个取路径名做参数,后两个则取⽂件名做参数。
当指定filename做参数时:
a. 如果filename中包含/,则将其视为路径名
b. 否则就按PATH环境变量搜索可执⾏⽂件。
②    参数传递⽅式:exec函数族的参数传递有两种⽅式,⼀种是逐个列举(l)的⽅式,⽽另⼀种则是将所有参数整体构造成指针数组(v)进⾏传递。
在这⾥参数传递⽅式是以函数名的第5位字母来区分的,字母为“l”(list)的表⽰逐个列举的⽅式,字母
为“v”(vertor)的表⽰将所有参数整体构造成指针数组传递,然后将该数组的⾸地址当做参数传给它,
数组中的最后⼀个指针要求是NULL。读者可以观察execl、execle、execlp的语法与execv、execve、execvp的区别。
③    环境变量:exec函数族使⽤了系统默认的环境变量,也可以传⼊指定的环境变量。这⾥以“e”(environment)结尾的两个函数execle、execve就可以在envp[]中指定当前进程所使⽤的环境变量替换掉该进程继承的所以环境变量。
(3)PATH环境变量说明
PATH环境变量包含了⼀张⽬录表,系统通过PATH环境变量定义的路径搜索执⾏码,PATH环境变量定义时⽬录之间需⽤⽤“:”分隔,以“.”号表⽰结束。PATH环境变量定义在⽤户的.profile或.bash_profile中,下⾯是PATH环境变量定义的样例,此PATH变量指定在“/bin”、“/usr/bin”和当前⽬录三个⽬录进⾏搜索执⾏码。
PATH=/bin:/usr/bin:.
export $PATH
(4)进程中的环境变量说明
在Linux中,Shell进程是所有执⾏码的⽗进程。当⼀个执⾏码执⾏时,Shell进程会fork⼦进程然后调⽤exec函数去执⾏执⾏码。Shell进程堆栈中存放着该⽤户下的所有环境变量,使⽤execl、execv、execlp、execvp函数使执⾏码重⽣
时,Shell进程会将所有环境变量复制给⽣成的新进程;⽽使⽤execle、execve时新进程不继承任何Shell进程的环境变量,⽽由envp[]数组⾃⾏设置环境变量。
(5)exec函数族关系
第4位统⼀为:exec
l:参数传递为逐个列举⽅式execl、execle、execlp
第5位
v:参数传递为构造指针数组⽅式execv、execve、execvp
e:可传递新进程环境变量execle、execve
第6位
p:可执⾏⽂件查⽅式为⽂件名execlp、execvp
事实上,这6个函数中真正的系统调⽤只有execve,其他5个都是库函数,它们最终都会调⽤execve这个系统调⽤,调⽤关系如下图12-11所⽰:
图12-11 exec函数族关系图
(6)exec调⽤举例如下
char *const ps_argv[] ={"ps", "-o", "pid,ppid,pgrp,session,tpgid,comm", NULL};
char *const ps_envp[] ={"PATH=/bin:/usr/bin", "TERM=console", NULL};
execl("/bin/ps", "ps", "-o", "pid,ppid,pgrp,session,tpgid,comm", NULL);
execv("/bin/ps", ps_argv);
execle("/bin/ps", "ps", "-o", "pid,ppid,pgrp,session,tpgid,comm", NULL, ps_envp);
execve("/bin/ps", ps_argv, ps_envp);
execlp("ps", "ps", "-o", "pid,ppid,pgrp,session,tpgid,comm", NULL);
execvp("ps", ps_argv);
请注意exec函数族形参展开时的前两个参数,第⼀个参数是带路径的执⾏码(execlp、execvp函数第⼀个参数是⽆路径的,系统会根据PATH⾃动查然后合成带路径的执⾏码),第⼆个是不带路径的执⾏码,执⾏码可以是⼆进制执⾏码和Shell脚本。
(7)exec函数族使⽤注意点
在使⽤exec函数族时,⼀定要加上错误判断语句。因为exec很容易执⾏失败,其中最常见的原因有:
①    不到⽂件或路径,此时errno被设置为ENOENT。
②    数组argv和envp忘记⽤NULL结束,此时errno被设置为EFAULT。
③    没有对应可执⾏⽂件的运⾏权限,此时errno被设置为EACCES。
(8)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重定向。
(9)execlp函数举例
execlp.c源代码如下:
#include <stdio.h>
#include <unistd.h>
int main()
{
if(fork()==0){
if(execlp("/usr/bin/env","env",NULL)<0)
{
perror("execlp error!");
return -1 ;
}
}
return 0 ;
}
编译 gcc execlp.c –o execlp。
执⾏ ./execlp,执⾏结果如下:
HOME=/home/test
DB2DB=test
SHELL=/bin/bash
……
由执⾏结果看出,execlp函数使执⾏码重⽣时继承了Shell进程的所有环境变量,其他三个不以e结尾的函数同理。
(10)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进程的所有环境变量。
总结⽰例exec.c:
/* exec函数族的语法 */
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
int main(int argc, char *argv[])
{
/* 字符串指针数组传递参数,使⽤包含v的exec函数参数 */
char *arg[] = {"ls","-a",NULL};
char *arg1[] = {"env",NULL};  //只⽤于execve函数
char *envp[] = {"NAME=amoscykl","EMAIL=xxxx@xx","PATH=/tmp",NULL};
char **ptr;    //指向环境表
/
/ 打印出环境表
printf("⾃定义环境表\n");
for (ptr = envp; *ptr != 0; ptr++)
printf("%s \n",*ptr);
printf("\n");
sleep(2);
/* ⼦进程调⽤execl函数 */
if (fork() == 0)
{
//child1
printf("1-----execl-----\n");
if (execl("/bin/ls","ls","-a",NULL) == -1)
linux下的sleep函数{
perror("execl error!");
exit(1);
}
}
sleep(2);
/* ⼦进程调⽤execv函数 */
if (fork() == 0)
{
//child2
printf("2-----execv-----\n");
if (execv("/bin/ls",arg) == -1)
{
perror("execv error!");
exit(1);
}
}
sleep(2);
/* ⼦进程调⽤execlp函数 */
if (fork() == 0)
{

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。