exec 函数族与执行新程序(分享)

      使用 fork()和 vfork() 创建子进程后,子程序通常会调用 exec 函数族来执行另外一个程序,这个 exec 函数族就提供了一个在进程中启动另一个程序执行的方法。它根据指定的文件名或目录名找到可执行文件,并用它来代替当前进程的执行映像。也就是说,exec调用并没有生成新进程,一个进程一旦调用 exec函数,它本身就“死亡”了,系统把代码段替换成新程序的代码,放弃原有的数据段和堆栈段,并为新程序分配新的数据段与堆栈段,惟一保留的就是进程的 ID。也就是说,对系统而言,还是同一个进程,不过执行的已经是另外一个程序了。

实际上,在 linux 中并没有 exec() 函数,而是有 6 个以 exec 开头的函数族,他们之间的语法有细微的差别,函数原型如下所示:

所需头文件:#include <unistd.h> 

函数原型:

引用
int execl(const char *path, const char *arg, ...)
int execv(const char *path, char *const argv[])
int execle(const char *path, const char *arg, ... , char *const envp[])
int execve(const char *path, char *const argv[], char *const envp[])
int execlp(const char *file, const char *arg, ...)
int execvp(const char *file, *const argv[])


理解 exec() 函数族的使用,首先要理解环境变量这个概念。

为了用户灵活地使用 shell ,Linux 引入了环境变量的概念,包括用户的主目录,终端类型、当前目录等,他们定义了用户的工作环境,所以成为环境变量。下面代码演示环境变量的应用:

引用
#include <stdio.h>
#include <malloc.h>

extern char **environ;

int main(int argc, char *argv[])
{
   int i;
  
   printf("Argument:\n");
   for(i=0;i<argc;i++){
       printf("argv[%d] is %s\n",i, argv[i]);
   }

   printf("Enviroment:\n");
   for(i=0;environ[i] != NULL;i++)
       printf("%s\n", environ[i]);

   return 0;
}

运行及输出:

引用
beyes@linux-beyes:~/C/base> ./env.exe arg1 arg2 arg3
Argument:
argv[0] is ./env.exe
argv[1] is arg1
argv[2] is arg2
argv[3] is arg3
Enviroment:
LESSKEY=/etc/lesskey.bin
ORBIT_SOCKETDIR=/tmp/orbit-beyes
INFODIR=/usr/local/info:/usr/share/info:/usr/info
NNTPSERVER=news
... ... ... ...

说明:
Argument 后面显式的是程序的命令行参数。
Enviroment 后面显示的是当前系统中各个环境变量的值,可以将其与 env 命令比较,将会发现两者的结果一样。

程序中通过系统预定义的环境变量 environ 显示各个环境变量的值。还可以通过另外一个方法得到环境变量值。

事实上, main() 函数的完整形式应该是:

引用
int main( int argc, char *argv[], **envp );


通过打印 main 的参数 envp ,同样可以得到环境变量。
 
 
 


 
 
事实上,无论哪个 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 是传递给解释器的参数。

--------------------------------------------------------------------------------
测试代码:

程序一:用来替换进程映像的程序

引用
#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() 函数

引用
#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 之外,进程还保持了许多原有的特征,主要有:

当前工作目录
根目录
创建文件时使用的屏蔽字
进程信号屏蔽字
未决警告
和进程相关的使用处理器的时间
控制终端
文件锁
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值