1. 定义
以exec
开头的函数,统称exec
函数:
#include <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[]);
这些函数如果调用成功则加载新的程序从启动代码开始执行,不再返回,如果调用出错则返回-1,所以exec
函数只有出错的返回值而没有成功的返回值。
2. 命名分析
(1)p & not p
不带字母p(表示path)的exec
函数第一个参数必须是程序的相对路径或绝对路径,例如"/bin/ls"
或"./a.out"
,而不能是"ls"
或"a.out"
。对于带字母p的函数:
-
如果参数中包含/,则将其视为路径名。
-
否则视为不带路径的程序名,在
PATH
环境变量的目录列表中搜索这个程序。
(2)l & v
带有字母l(表示list)的exec
函数要求将新程序的每个命令行参数都当作一个参数传给它,命令行参数的个数是可变的,因此函数原型中有...
,...
中的最后一个可变参数应该是NULL
,起sentinel的作用。对于带有字母v(表示vector)的函数,则应该先构造一个指向各参数的指针数组,然后将该数组的首地址当作参数传给它,数组中的最后一个指针也应该是NULL
,就像main
函数的argv
参数或者环境变量表一样。
(3)e
对于以e(表示environment)结尾的exec
函数,可以把一份新的环境变量表传给它,其他exec
函数仍使用当前的环境变量表执行新程序。
3. 实例
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);
事实上,只有execve
是真正的系统调用,其它五个函数最终都调用execve
,所以execve
在man手册第2节,其它函数在man手册第3节。这些函数之间的关系如下图所示。
图 30.5. exec函数族
一个完整的例子:
#include <unistd.h> #include <stdlib.h> int main(void) { execlp("ps", "ps", "-o", "pid,ppid,pgrp,session,tpgid,comm", NULL); perror("exec ps"); exit(1); }
执行此程序则得到:
$ ./a.out
PID PPID PGRP SESS TPGID COMMAND
6614 6608 6614 6614 7199 bash
7199 6614 7199 6614 7199 ps
由于exec
函数只有错误返回值,只要返回了一定是出错了,所以不需要判断它的返回值,直接在后面调用perror
即可。注意在调用execlp
时传了两个"ps"
参数,第一个"ps"
是程序名,execlp
函数要在PATH
环境变量中找到这个程序并执行它,而第二个"ps"
是第一个命令行参数,execlp
函数并不关心它的值,只是简单地把它传给ps
程序,ps
程序可以通过main
函数的argv[0]
取到这个参数。
exec
后,原来打开的文件描述符仍然是打开的[37]。