在进程控制(1)中,介绍了创建子进程fork和vfork函数,其实在创建一个进程之后,子进程往往会调用一个exec函数去执行另一个程序。当调用一个exec函数之后,该进程执行的程序完全替换为新程序,而新程序从main函数开始执行。exec函数并不创建新的进程,前后进程ID不变,只是用磁盘上的一个新程序替换了当前进程的正文段、数据段、堆段和栈段。
exec函数
#include <unistd.h>
int execl(const char *pathname, const char *arg0, ... /* (char *)0 */);
int execv(const char *pathname, char *const argv[]);
int execle(const char *pathname, const char *arg0, ... /* (char *)0, char *const envp[] */);
int execve(const char *pathname, char *const argv[], char *const envp[]);
int execlp(const char *filename, const char *arg0, ... /* (char *)0 */);
int execvp(cosnt char *filename, char *const argv[]);
6个函数的返回值:若出错则返回-1,若成功则没有返回值,其中只有execve函数属于内核的系统调用,其他的只是库函数
第一个区别
前四个取路径名做为参数,后两个取文件名做为参数,当指定filename作为参数时:
1、如果文件名中不包含“/”则从PATH环境变量中搜寻可执行文件。
2、包含“/”就当做路径名。
PATH 环境变量包含一张目录表(称为路径前缀),目录之间用冒号(:)分割。例如,
PATH=/bin:/usr/bin:/usr/local/bin:.
指定了四个目录项,最后一个路径前缀是当前目录。(零长前缀也表示当前目录,在 value 的开始处可用: 表示,在行中间则要用:: 表示,在行尾则以:表示。)
如果execlp或execv使用路径前缀找到了一个可执行文件,但是该文件不是连接编辑程序产生的可执行代码文件,则当做shell脚本处理。第二个区别
前两个和最后一个函数中都包括“ l ”这个字母,而另三个都包括“ v”, ” l “代表 list即表,而” v “代表 vector即矢量,也就是前三个函数的参数都是以list的形式给出的,要求将新程序的每个命令行参数都说明为一个单独的参数,但最后要加一个空指针,如果用常数0来表示空指针,则必须将它强行转换成字符指针,否则有可能出错。而后三个应该先构造一个指向各参数的指针数组,然后将该数组地址作为这几个函数的参数。第三个区别
与向新程序传递环境表有关,以e结尾的函数,可以向函数传递一个指向环境字符串指针数组的指针。即自个定义各个环境变量,而其它四个则使用调用进程中的environ变脸为新程序复制现有的环境。
这六个函数中只有execve函数属于内核的系统调用,其他的只是库函数,这几个函数之间的关系可以表示为下图:
下面给出几个具体的例子来说明这几个函数的用法:
1、使用execl和execv来传递参数
#include "apue.h"
int main(int argc, char *argv[])
{
int i;
for (i = 1; i < argc; i++) {
printf("arg%d: %s\n", i, argv[i]);
}
exit(0);
}
生成上面程序的可执行文件后,在demo中用使用execl和execv。
#include"apue.h"
#include <sys/wait.h>
char demoPath[] = "/home/xucheng/xc/test/print";
char *myargs[] = {"print", "xucheng", "ha", "ha", "ha" };
int
main(void)
{
pid_t pid;
if ((pid = fork()) < 0) {
err_sys("fork error");
} else if (pid == 0) {
printf("Transmits by vector\n");
if (execv(demoPath, myargs) < 0) {
err_sys("execv error");
}
}
if(waitpid(pid,NULL,0)<0)
err_sys("wait error");
if ((pid = fork()) < 0) {
err_sys("fork error");
} else if (pid == 0) {
printf("Transmits by list\n");
if (execl(demoPath, "another",myargs[0], myargs[1],
myargs[2], myargs[3], myargs[4], (char *)0) < 0) {
err_sys("execv error");
}
}
if(waitpid(pid,NULL,0)<0)
err_sys("wait error");
exit(0);
}
运行的结果为:
xucheng@xucheng-Inspiron-5520:~/xc/test$ gcc demo.c -o demo
xucheng@xucheng-Inspiron-5520:~/xc/test$ ./demo
Transmits by vector
arg1: xucheng
arg2: ha
arg3: ha
arg4: ha
Transmits by list
arg1: print
arg2: xucheng
arg3: ha
arg4: ha
arg5: ha
需要特别说明一点,在调用 execl、execle 和 execlp 函数传递参数表,在最后一个命令行参数之后跟着一个空指针。如果要用常量 0 来表示空指针,则必须要将它转换为一个字符指针。
2、使用带有环境变量的exec函数
以e结尾的3个函数可以传递一个指向环境字符串指针数组的指针。
下面这个函数会打印出所有的环境字符串。
#include"apue.h"
int main(void)
{
extern char **environ;
char **ptr;
for (ptr = environ; *ptr != NULL; ptr++) {
printf("%s\n", *ptr);
}
exit(0);
}
编译运行后,可以看到:
xucheng@xucheng-Inspiron-5520:~/xc/test$ ./print
XDG_VTNR=7
LC_PAPER=zh_CN.UTF-8
LC_ADDRESS=zh_CN.UTF-8
XDG_SESSION_ID=c2
XDG_GREETER_DATA_DIR=/var/lib/lightdm-data/xucheng
LC_MONETARY=zh_CN.UTF-8
......
下面在demo中使用execv函数和execve函数,调用上面生成的可执行文件,比较打印的环境变量列表有何区别。
#include"apue.h"
#include<sys/wait.h>
char demoPath[] = "/home/xucheng/xc/test/print";
char *myEnvs[] = { "env1=hhhhhh", "env2=aaaaaaa" };
int main(void)
{
pid_t pid;
if (putenv("newenv=add in parent process") < 0) {
err_sys("putenv error");
}
if ((pid = fork()) < 0) {
err_sys("fork error");
} else if (pid == 0) {
printf("print_envir by execv\n");
if (execv(demoPath, NULL) < 0) {
err_sys("execv error");
}
}
if(waitpid(pid,NULL,0)<0)
err_sys("wait error");
if ((pid = fork()) < 0) {
err_sys("fork error");
} else if (pid == 0) {
printf("\n print_envir by execve\n");
if (execve(demoPath, NULL, myEnvs) < 0) {
err_sys("execv error");
}
}
if(waitpid(pid,NULL,0)<0)
err_sys("wait error");
exit(0);
}
运行结果如下:
xucheng@xucheng-Inspiron-5520:~/xc/test$ ./demo
print_envir by execv
XDG_VTNR=7
LC_PAPER=zh_CN.UTF-8
LC_ADDRESS=zh_CN.UTF-8
......
XAUTHORITY=/home/xucheng/.Xauthority
OLDPWD=/home/xucheng
_=./demo
newenv=add in parent process
print_envir by execve
env1=hhhhhh
env2=aaaaaaa
从上面的运行结果,我们看到调用 execv 函数时父进程会将其设置的环境变量也传递给了子进程。而调用execve 函数时,子进程的环境变量列表只有 execve 函数传递的 列表。
3、解释器文件
exec函数也可以通过解释器文件来执行新的程序,解释器文件其实是一种文本文件,形式如下:
#! pathname[optional-argument]
内核使调用exec函数的进程实际执行的不是该解释器文件,而是在该解释器文件中第一行pathname中所指定的文件。如下:
#include "apue.h"
#include <sys/wait.h>
int
main(void)
{
pid_t pid;
if ((pid = fork()) < 0) {
err_sys("fork error");
} else if (pid == 0) { /* child */
if (execl("/home/xucheng/xc/test/temp",
"temp", "myarg1", "MY ARG2", (char *)0) < 0)
err_sys("execl error");
}
if (waitpid(pid, NULL, 0) < 0) /* parent */
err_sys("waitpid error");
exit(0);
}
运行之前要更该解释器文件的权限,运行之后,结果为:
xucheng@xucheng-Inspiron-5520:~/xc/test$ cat temp
#! /home/xucheng/xc/test/print foo
xucheng@xucheng-Inspiron-5520:~/xc/test$ ./demo
arg0: /home/xucheng/xc/test/print
arg1: foo
arg2: /home/xucheng/xc/test/temp
arg3: myarg1
arg4: MY ARG2
其中argv[0]是解释器文件的pathname,argv[1]是解释器文件中的可选参数。