Linux系统编程之进程替换:exec函数族

284 阅读5分钟

  在Windows平台下,我们可以通过双击运行可执行程序,让这个可执行程序成为一个进程;而在Linux平台,我们可以通过./运行,让一个可执行程序成为一个进程。

  但是,如果我们本来就运行着一个程序(进程),我们如何在这个进程内部启动一个外部程序,由内核将这个外部程序读入内存,使其执行起来成为一个进程呢?这里我们通过exec函数族实现。

  exec函数族,顾名思义,就是一簇函数,在Linux中,并不存在exec()函数,exec指的是一组函数,一共有6个:

  #include<unistd.h>

  int execl(const charpath,const chararg,...);

  int execlp(const charfile,const chararg,...);

  int execle(const charpath,const chararg,...,char*const envp[]);

  int execv(const charpath,charconst argv[]);

  int execvp(const charfile,charconst argv[]);

  int execve(const charpath,charconst argv[],char*const envp[]);

  其中只有execve()是真正意义上的系统调用,其它都是在此基础上经过包装的库函数。

  exec函数族提供了六种在进程中启动另一个程序的方法。exec函数族的作用是根据指定的文件名或目录名找到可执行文件,并用它来取代调用进程的内容,换句话说,就是在调用进程内部执行一个可执行文件。

  进程调用一种exec函数时,该进程完全由新程序替换,而新程序则从其main函数开始执行。因为调用exec并不创建新进程,所以前后的进程ID(当然还有父进程号、进程组号、当前工作目录……)并未改变。exec只是用另一个新程序替换了当前进程的正文、数据、堆和栈段(进程替换)。   

  exec函数族的6个函数看起来似乎很复杂,但实际上无论是作用还是用法都非常相似,只有很微小的差别。   

  l(list):参数地址列表,以空指针结尾。

  v(vector):存有各参数地址的指针数组的地址。

  p(path):按PATH环境变量指定的目录搜索可执行文件。

  e(environment):存有环境变量字符串地址的指针数组的地址。

  exec函数族装入并运行可执行程序path/file,并将参数arg0(arg1,arg2,argv[],envp[])传递给此程序。

  exec函数族与一般的函数不同,exec函数族中的函数执行成功后不会返回,而且,exec函数族下面的代码执行不到。只有调用失败了,它们才会返回-1,失败后从原程序的调用点接着往下执行。

  excel代码

  #include<stdio.h>

  #include<unistd.h>

  int main(int argc,char*argv[])

  {

  printf("before exec\n\n");

  /*/bin/ls:外部程序,这里是/bin目录的ls可执行程序,必须带上路径(相对或绝对)

  ls:没有意义,如果需要给这个外部程序传参,这里必须要写上字符串,至于字符串内容任意

  -a,-l,-h:给外部程序ls传的参数

  NULL:这个必须写上,代表给外部程序ls传参结束

  */

  execl("/bin/ls","ls","-a","-l","-h",NULL);

  //如果execl()执行成功,下面执行不到,因为当前进程已经被执行的ls替换了

  perror("execl");

  printf("after exec\n\n");

  return 0;

  }

  运行结果:   

  execv()示例代码:

  execv()和execl()的用法基本是一样的,无非将列表传参,改为用指针数组。

  #include<stdio.h>

  #include<unistd.h>

  int main(int argc,char*argv[])

  {

  //execv()和execl()的用法基本是一样的,无非将列表传参,改为用指针数组

  //execl("/bin/ls","ls","-a","-l","-h",NULL);

  /*指针数组

  ls:没有意义,如果需要给这个外部程序传参,这里必须要写上字符串,至于字符串内容任意

  -a,-l,-h:给外部程序ls传的参数

  NULL:这个必须写上,代表给外部程序ls传参结束

  */

  char*arg[]={"ls","-a","-l","-h",NULL};

  ///bin/ls:外部程序,这里是/bin目录的ls可执行程序,必须带上路径(相对或绝对)

  //arg:上面定义的指针数组地址

  execv("/bin/ls",arg);

  perror("execv");

  return 0;

  }

  execlp()或execvp()示例代码:

  execlp()和execl()的区别在于,execlp()指定的可执行程序可以不带路径名,如果不带路径名的话,会在环境变量PATH指定的目录里寻找这个可执行程序,而execl()指定的可执行程序,必须带上路径名。

  #include<stdio.h>

  #include<unistd.h>

  int main(int argc,char*argv[])

  {

  //第一个参数"ls",没有带路径名,在环境变量PATH里寻找这个可执行程序

  //其它参数用法和execl()一样

  execlp("ls","ls","-a","-l","-h",NULL);

  /*

  char*arg[]={"ls","-a","-l","-h",NULL};

  execvp("ls",arg);

  */

  perror("execlp");

  return 0;

  }

  execle()或execve()示例代码:

  execle()和execve()改变的是exec启动的程序的环境变量(只会改变进程的环境变量,不会影响系统的环境变量),其他四个函数启动的程序则使用默认系统环境变量。

  execle()示例代码:

  #include<stdio.h>

  #include<unistd.h>

  #include<stdlib.h>//getenv()

  int main(int argc,char*argv[])

  {

  //getenv()获取指定环境变量的值

  printf("before exec:USER=%s,HOME=%s\n",getenv("USER"),getenv("HOME"));

  //指针数据

  char*env[]={"USER=EDU","HOME=/tmp",NULL};

  /*./edu:外部程序,当前路径的edu程序,通过gcc edu.c-o edu编译

  edu:这里没有意义

  NULL:给edu程序传参结束

  env:改变edu程序的环境变量,正确来说,让edu程序只保留env的环境变量

  */

  execle("./edu","edu",NULL,env);

  /*

  char*arg[]={"edu",NULL};

  execve("./edu",arg,env);

  */

  perror("execle");

  return 0;

  }

  外部程序,edu.c示例代码:

  #include<stdio.h>

  #include<stdlib.h>

  #include<unistd.h>

  int main(int argc,char*argv[])

  {

  printf("\nin the edu fun,after exec:\n");

  printf("USER=%s\n",getenv("USER"));

  printf("HOME=%s\n",getenv("HOME"));

  return 0;

  }

  运行结果: