事实上,无论哪个 exec 函数,都是将可执行程序的路径,命令行参数和环境变量 3 个参数传递给可执行程序的 main() 函数。
下面分别介绍各 exec 函数是如何将 main 函数需要的参数传递给它的。
execv() 函数:execv() 通过路径名方式调用可执行文件作为新的进程的映像。它的 argv 参数用来提供给 main() 的 argv 参数。argv 参数是一个以 NULL 结尾 ( 最后一个元素必须是空指针 )的字符串数组。 execve() 函数:在该系统调用中,参数 path 是将要执行的程序的路径名,参数 argv、envp 与 main 函数的 argv、envp 对应。 注意:参数 argv 和参数 envp 的大小都是受限制的。linux 系统通过宏 ARG_MAX 来限制她们的大小,如果它们的容量之和超过 ARG_MAX 定义的值将会发生错误。这个宏定义在 <linux/limits.h> 头文件中。
execl() 函数:该函数与 execv() 函数用法类似。只是在传递 argv 参数的时候,每个命令行参数都声明为一个单独的参数( 参数中使用 “..." 说明参数的个数是不确定的 ),要注意的是这些参数要以一个空指针作为结束。 execle() 函数:该函数与 execl 函数用法类似,只是要显式指定环境变量。环境变量位于命令行参数最后一个参数的后面,也就是位于空指针之后。 execvp() 函数:该函数和 execv() 函数用法类似,不同的是参数 filename。该参数如果包含 “/”,就相当于路径名;如果不包含 "/",函数就到 PATH 环境变量定义的目录中寻找可执行文件。 execlp() 函数:该函数类似于 execl() 函数,他们的区别和 execvp() 与 execv() 的区别一样。 exec 函数族的 6 个函数中只有 execve() 是系统调用。其它 5 个都是库函数,他们实现时都调用 execve() 。
由上面注意到,有 2 个函数( 以 p 结尾的两个函数 )可以只给出文件名,系统就会自动从环境变量 $PATH 所指出的路径中进行查找,其余 4 个函数的查找方式都是完整的文件目录路径。
关于参数的传递方式:
exec 函数族的参数传递方式有两种:一种是列举方式;一种是将所有参数整体构造指针数组传递。
在这里,是以函数名的第 5 位字母来区分的,字母为" l " ( list ) 的表示逐个列举方式,其语法为 char *arg;
字母为" v "( vector ) 的表示将所有参数整体构造指针数组传递,其语法为 *const argv[] 。
在正常情况下,这些函数不会返回的,因为进程的执行映像已经被替换,没有接收返回值的地方了。如果有一个错误时间,将返回 -1。这些错误通常是由文件名或参数错误引起的。其含义如下表所示:
errno
| 错误描述
| EACCES
| 指向的文件或脚本文件没有设置可执行位,即指定的文件是不可执行的
| E2BIG
| 新程序的命令行参数与环境变量容量之和超过 ARG_MAX
| ENOEXEC
| 没有正确的格式,指定的文件无法执行
| ENOMEM
| 没有足够的内存空间来执行指定的程序
| ETXTBUSY
| 指定的文件被一个或多个进程以可写的方式打开
| EIO
| 从文件系统读入文件时发生 I/O 错误
|
在 linux 系统下,exec 函数族可以执行二进制的可执行文件,也可以执行 shell 脚本程序,但 shell 脚本必须以下面所示的格式开头:
第一行必须为: #!interpretername [arg] 。
其中 interpretername 可以是 shell 或其它解释器,例如 /bin/sh 或 /usr/bin/perl,arg 是传递给解释器的参数。
测试代码:
程序一:用来替换进程映像的程序
[C++] 纯文本查看 复制代码 include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main(int argc, char *argv[], char **environ)
{
int i;
printf("I am a process image\\n");
printf("My pid = %d, parentpid = %d\\n", getpid(), getppid());
printf("uid = %d ,gid = %d\\n", getuid(), getgid());
for(i = 0; i < argc; i++)
printf("argv[%d]:%s\\n", i, argv[i]);
}
程序二:exec 函数实例,这里用了 execve() 函数
[C++] 纯文本查看 复制代码 #include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
int main(int argc, char *argv[], char **environ)
{
pid_t pid;
int stat_val;
printf("Exec example\\n");
pid = fork();
switch(pid) {
case -1:
perror("Process Creation failed\\n");
exit(1);
case 0:
printf("Child process is running\\n");
printf("My pid = %d, parentpid = %d\\n", getpid(), getppid());
printf("uid = %d, gid = %d\\n", getuid(), getgid());
execve("processimage.exe", argv, environ);
printf("process never go to hear!\\n");
exit(0);
default:
printf("Parent process is running\\n");
break;
}
wait(&stat_val);
exit(0);
}
运行及输出: beyes@linux-beyes:~/C> ./execve.exe
Exec example
Child process is running
My pid = 31369, parentpid = 31368
uid = 1000, gid = 100
I am a process image
My pid = 31369, parentpid = 31368
uid = 1000 ,gid = 100
argv[0]:./execve.exe
Parent process is running
说明:
从执行结果可以看到,执行新程序保持了原来进程的进程 ID、父进程ID、实际用户 ID 和实际组 ID。同时还可以看到,当调用新的可执行程序后,原有的子进程的映像被替代,不再被执行。子进程永远不会执行到 printf("process never go to here!\\n"); ,因为子进程已经被新的执行映像所替代。
执行新程序后的进程除了保持原来的进程 ID、父进程 ID、实际用户 ID 和实际组 ID 之外,进程还保持了许多原有的特征,主要有:
下面示例演示 execle() 和 execlp() 的用法。
1. 在 execle() 和在 execlp() 里调用的程序 echoall 代码:
[C++] 纯文本查看 复制代码 #include <stdio.h>
int main(int argc, char *argv[])
{
int i;
char **ptr;
extern char **environ;
for (i = 0; i < argc; i++) /*echo all comman-line args*/
printf ("argv[%d] : %s\\n", i, argv[i]);
for (ptr = environ; *ptr != 0; ptr++)
printf ("%s\\n", *ptr);
return (0);
}
上面代码的两个 for 循环中分别打印了命令行上的所有参数以及所有的环境变量。环境变量 environ 也是个指针数组。
具有 execle() 和 execlp() 函数演示的代码:
[C++] 纯文本查看 复制代码 #include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>
char *env_init[] = { "USER=unknow", "PATH=/tmp", NULL };
void err_sys (char *str)
{
perror (str);
exit (EXIT_FAILURE);
}
int main(void)
{
pid_t pid;
if ((pid = fork()) < 0) {
err_sys("fork error");
} else if (pid == 0) {
if (execle("/root/bin/echoall", "echoall", "myarg1", "MY ARG2", (char *)0, env_init) < 0) {
err_sys("execle error");
}
}
if (waitpid (pid, NULL, 0) < 0) {
err_sys("waitpid error");
}
if ((pid = fork()) < 0) {
err_sys ("fork error");
} else if (pid == 0) {
if (execlp ("echoall", "echoall", "only 1 arg", (char *)0) < 0)
err_sys ("execlp error");
}
exit (EXIT_SUCCESS);
}
由于这里是用 root 用户来运行这段程序(不一定非得要用root来执行),所以在运行上面程序之前,要将 echoall 拷贝到 /root/bin 目录下,因为这个目录是 $PATH 环境变量里包含的目录之一,所以在执行时 execlp() 能够从 $PATH 的目录中搜索到 echoall 。
在 execle() 和 execlp() 的将参数列表里第一个参数(echoall)设置为文件名,即 argv[0] 为 echoall 。一般情况下,如果在 shell 里直接用指定路径的方式来执行 echoall ,那么 argv[0] 就会显示执行时的全路径,比如:$ /home/beyes/C/syscall/exec/echoall
argv[0] : /home/beyes/C/syscall/exec/echoall 或$ ./echoall
argv[0] : ./echoall 实际上,argv[0] 我们完全可以自定义为任意的字符串;路径名的显示方式只是一种惯例。
在 execle() 中(execve() 函数也是),最后的一个参数可以传递一个自定义的指向环境字符串(这些字符串也自定义,如像上面程序中的 env_init )指针数组的指针;其它的函数则使用调用进程中的 environ 变量为新程序复制现有的环境。 |