Linux exec函数族

当我们看恐怖片时,经常会有这样的场景:当一个人被鬼上身后,这个人的身体表面上还和以前一样,但是他的灵魂和思想已经被这个鬼占有了,因此它会控制这个人做他自己想做的事情--那么在进程中也有这样的情景。那么是如何实现的呢?现在我们来学习exec()函数族

一.exec()函数 族

1. 首先我们在终端输入命令:man 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*pathname, const char *argv[], char *const envp[]);
int execlp(const char *file, const char *arg, ...);
int execvp(const char *file, char *const argv[]);
这些函数之间的第一个区别 是前4个取路径名做参数,后两个则取文件名做参数。
当指定filename做参数时:
a. 如果filename中包含/,则将其视为路径名
b. 否则就按PATH环境变量搜索可执行文件。
PATH=/bin:/usr/bin:/user/local/bin:.
最后的路径前缀表示当前目录。零前缀耶表示当前目录(在name=value开始处可用:表示,在中间用::表示,在行尾用:表示)
第二个区别 与参数表的传递有关(l表示list,v表示矢量vector)。函数execl、execlp和execle要求将新程序的每个命令行参数都说明为一个单独的参数,这中参数表以空指针结尾。而execv、execve和execvp则要先构造一个指向各参数的指针数组,然后将该数组的地址作为这三个函数的地址。
第三个区别 与向新程序传递环境表相关。函数execve和execle可以传递一个指向环境字符串指针数组的指针。

Tiger-John总结:
1>.其中只有execve是真正意义上的系统调用,其它都是在此基础上经过包装的库函数。
2>.exec函数族的作用是根据指定的文件名找到可执行文件,并用它来取代调用进程的内容 ,换句话说,就是在调用进程内部执行一个可执行文件。这里的可执行文件既可以是二进制文件,也可以是任何Linux下可执行的脚本文件。

但是若为Shell脚本时必须遵守以下的格式开头:

第一行必须为: #!interpretername[arg]。其中interpretername可以时 shell或其他解释器。例如,/bin/sh或/usr/bin/perl,arg是传递个解释器的参数。

3>记忆方法:

l : 表示使用参数列表(list)

e:表示使用新的环境变量,不从当前继承

p: 表示使用文件名,并从PATH环境进行搜索

4> exec()函数族成功后是不会返回值的,因为进程的执行映像已经被替换,没有接收返回值的地方了。但是若有一个错误的事件,将会返回-1.这些错误通常是有文件名或参数错误引起的。

2.exec()函数的功能:

1>exec()函数调用并没有生成新进程,一个进程一旦调用exec函数,它本省就“死亡了”--就好比被鬼上身一样,身体还是你的,但灵魂和思想已经被替换了 --系统把代码段替换成新的程序的代码,废弃原有的数据段和堆栈段,并为新程序分配新的数据段与堆栈段,唯一保留的就是进程ID。也就是说,对系统而言,还是同一个进程,不过执行的已经是另外一个程序了。

2>执行exec()函数后的进程除了保持了原来的进程ID,父进程ID,实际用户ID和实际组ID之外,进程还保持了其他许多原有特征,主要有

a.当前工作目录

b.根目录

c.创建文件时使用的屏蔽字

d.进程信号屏蔽字。

e. 未决警告

f.和进程相关的使用处理器的时间

g.控制终端

h.文件锁

 

理解 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;
}

运行及输出:

引用
alion@ubuntu~> ./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 ,同样可以得到环境变量。

 

 

 


Tiger-John说明:

1.此处要分清只有fork()或vfork()函数才能创建一个新进程,而exec()函数时不能创建进程的。

2.因此在使用exec()函数之前,先要使用fork()或vfork()创建子进程后,子进程调用exec()函数来执行另外一个程序。

3.exec()函数族的具体实现

1>当进程调用一种exec()函数时,该进程执行的程序完全替换为新程序,而新程序则从main函数开始执 行 。因为调用exec()并不创建新进程,所以前后的进程ID不变。函数exec()只是用一个全新的程序替换当前进程的正文、数据、堆和栈段。
2>无论是哪个exec()函数,都是将可执行程序的路径,命令行参数和环境变量3个参数传递个可执行程序的main()函数 。

3>具体介绍exec()函数族是如何main()函数需要的参数传递个它的。

a.execv()函数:execv()函数是通过路径名方式调用可执行文件作为新的进程映像。它的argv参数用来提供给main()函数的argv参数。argv参数是一个以NULL结尾的字符串数组

b.execve()函数:参数pathname时将要执行的程序的路径名,参数argv,envp 与main()函数的argv,envp对应 。

c.execl()函数:次函数与execv函数用法类似。只是在传递argv 参数的时候,每个命令行参数都声明为一个单独的参数(参数中使用“......"说明参数的个数是不确定的),要注意的是这些参数要以一个空指针作为结束。

d.execle()函数:该函数与execl函数用法类似,只是要显示指定环境变量。环境变量位于命令行参数最后一个参数的后面,也就是位于空指针之后。

e.execvp函数:该函数和execv函数用法类似,不同的是参数filename。该参数 如果包含/,则将其视为路径名,否则就按PATH环境变量搜索可执行文件。

f.execlp()函数:该函数于execl函数类似,它们的区别和execvp与execv的区别一样。

----------------------------------------------

通过以上学习,我们来编个程序来体验下它的执行过程

4.函数实例

exec.c

  1 #include<stdio.h>
  2 #include<sys/types.h>
  3 #include<unistd.h>
  4 #include<stdlib.h>
  5 int main(int argc,char *argv[],char ** environ)
  6 {
  7         pid_t pid;
  8         int status;
  9         printf("Exec example!/n");
10         pid = fork();
11         if(pid < 0){
12                 perror("Process creation failed/n");
13                 exit(1);
14         }
15         else if(0 == pid){
16                 printf("child process is running/n");
17                 printf("My pid = %d ,parentpid = %d/n",getpid(),getppid());
18                 printf("uid = %d,gid = %d/n",getuid(),getgid());
19                 execve("processimage",argv,environ);
20                 printf("process never go to here!/n");
21                 exit(0);
22         }
23         else {
24                 printf("Parent process is runnig/n");
25         }
26         wait(&status);
27         exit(0);
28 }

processimage.c

1 #include<stdio.h>
  2 #include<sys/types.h>
  3 #include<unistd.h>
  4
  5 int main(int argc,char * argv[],char ** environ)
  6 {
  7         int i;
  8
  9         printf("I am a process image!/n");
10         printf("My pid =%d,parentpid = %d/n",getpid(),getppid());
11         printf("uid = %d,gid = %d/n",getuid(),getpid());
12
13         for(i = 0;i<argc;i++){
14                 printf("argv[%d]:%s/n",i,argv[i]);
15         }
16
17 }

函数经过编译:

think@ubuntu:~/work/process_thread/exec1$ gcc processimage.c -o processimage
think@ubuntu:~/work/process_thread/exec1$ gcc exec.c -o exec

think@ubuntu:~/work/process_thread/exec1$ ./exec test exec

函数执行结果:

Exec example!
Parent process is runnig
child process is running
My pid = 5949 ,parentpid = 5948
uid = 1000,gid = 100
I am a process image!
My pid =5949,parentpid = 5948
uid = 1000,gid = 100
argv[0]:./exec
argv[1]:test
argv[2]:exec
Tiger-John说明:

1.通过上面程序的执行,我们可以看到执行新程序的进程保持了原来进程的进程ID,父进程ID,实际用户ID和实际组ID。

2. 当调用execve()函数后,原来的子进程的映像被替代,不再执行。

 

 

exec使用实例


下面的第一个示例说明了如何使用文件名的方式来查找可执行文件,同时使用参数列表
的方式。这里用的函数是execlp。
/*execlp.c*/
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main()
{
if(fork()==0){
/*调用execlp函数,这里相当于调用了“ps -ef”命令*/
if(execlp("ps","ps","-ef",NULL)<0)
perror("execlp error!");
}
}
在该程序中,首先使用fork函数新建一个子进程,然后在子进程里使用execlp函数。读
者可以看到,这里的参数列表就是在shell中使用的命令名和选项。并且当使用文件名的方式
进行查找时,系统会在默认的环境变量PATH 中寻找该可执行文件。

 

 

接下来的示例2 使用完整的文件目录来查找对应的可执行文件。注意目录必须以“/”开
头,否则将其视为文件名。
/*execl.c*/
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main()
{
if(fork()==0){
/*调用execl函数,注意这里要给出ps程序所在的完整路径*/
if(execl("/bin/ps","ps","-ef",NULL)<0)
perror("execl error!");
}
}
 
 
示例3 利用函数execle,将环境变量添加到新建的子进程中去,这里的“env”是查看当
前进程环境变量的命令,如下所示:
/*execle*/
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main()
{
/*命令参数列表,必须以NULL结尾*/
char *envp[]={"PATH=/tmp","USER=sunq",NULL};
if(fork()==0){
/*调用execle函数,注意这里也要指出env的完整路径*/
if(execle("/bin/env","env",NULL,envp)<0)
perror("execle error!");
}
}
下载到目标板后的运行结果如下所示:
[root@(none) 1]# ./execle
PATH=/tmp
USER=sunq
 
 
最后一个示例使用execve函数,通过构造指针数组的方式来传递参数,注意参数列表一
定要以NULL作为结尾标识符。其代码和运行结果如下所示:
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main()
{
/*命令参数列表,必须以NULL结尾*/
char *arg[]={"env",NULL};
char *envp[]={"PATH=/tmp","USER=sunq",NULL};
if(fork()==0){
if(execve("/bin/env",arg,,envp)<0)
perror("execve error!");
}
}
下载到目标板后的运行结果如下所示:
[root@(none) 1]# ./execve
PATH=/tmp
USER=sunq
 
使用范例
 

int execl(const char* fullpath, const char* arg, ....)
使用范例:execl(“/bin/ls”, ”ls”, ”-al”, NULL)

 

int execlp(const char* file, const char* arg, ....)
使用范例:execlp(“ls”, ”ls”, ”-al”, NULL)

 

int execle(const char* fullpath, const char* arg, ...., char* const envp[])
使用范例:execle(“/bin/ls”, ”ls”, ”-al”, NULL, environ)

 

int execv(const char * fullpath, char* const argv[])
使用范例:execv(“/bin/mkdir”, argv)  // int main(int argc, char* argv[])

char* const p[] = {"a.out", "testDir", NULL};
execv("/bin/mkdir", p);

 

int execvp(const char* file, const char* arg, ....)
使用范例:execlp(“ls”, argv)  // int main(int argc, char* argv[])

char* const p[] = {"a.out", "testDir", NULL};
execvp("mkdir", p);

 

int execve(const char* fullpath, const char* arg, ...., char* const envp[])
使用范例:execve(“/bin/ls”, argv, environ)

char* const p[] = {"a.out", "testDir", NULL};
execve("/bin/mkdir", p);

 

exec函数族使用注意点

在使用exec 函数族时,一定要加上错误判断语句。因为exec 很容易执行失败,其中最
常见的原因有:
· 找不到文件或路径,此时errno被设置为ENOENT;
· 数组 argv和envp忘记用NULL结束,此时errno被设置为EFAULT;
· 没有对应可执行文件的运行权限,此时errno被设置为EACCES。
posted @ 2013-04-11 09:27  ~风~  阅读(754)  评论(0编辑  收藏  举报