zoukankan      html  css  js  c++  java
  • UNIX环境编程学习笔记(20)——进程管理之exec 函数族

    lienhua34
    2014-10-07

    在文档“进程控制三部曲”中,我们提到 fork 函数创建子进程之后,通常都会调用 exec 函数来执行一个新程序。调用 exec 函数之后,该进程就将执行的程序替换为新的程序,而新的程序则从 main 函数开始执行。

    UNIX 提供了 6 种不同的 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,若成功则没有返回值

    可能很多人会觉得这六个函数太难记了。但是,我们仔细观察会发现,这六个函数的命名是有一些规律的。

    • 含有 l 和 v 的 exec 函数的参数表传递方式是不同的。

    • 含有 e 结尾的 exec 函数会传递一个环境变量列表。

    • 含有 p 结尾的 exec 函数取的是新程序的文件名作为参数,而其他exec 函数取的是新程序的路径。

    1 exec 函数的参数表传递方式

    exec 函数给新程序传递参数表方式的不同可以通过 exec 函数名称来体现。含有 l(l 表示 list)的 exec 函数(execl、execle 和 execlp)将新程序的参数表以列表的方式传递,要求每个命令行参数作为一个单独的参数,最后空指针结尾。含有 v(v 表示 vector)的 exec 函数(execv、execve 和execvp)将新程序的参数表构造成一个数组进行传递。下面我们来看一个例子,我们有一个程序 echoargs.c,其输出所有的命令行参数。

    #include <stdio.h>
    #include <stdlib.h>
    
    int
    main(int argc, char *argv[])
    {
      int i;
      for (i = 1; i < argc; i++) {
        printf("arg%d: %s
    ", i, argv[i]);
      }
      exit(0);
    }

    编译该程序,生成 echoargs 文件,

    lienhua34:demo$ gcc -o echoargs echoargs.c

    然后在我们的 execdemo.c 中分别以两种不同参数表传递方式来调用execl 和 execv 函数,

    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <errno.h>
    #include <string.h>
    #include <sys/wait.h>
    
    char echoargsPath[] = "/home/lienhua34/program/c/apue/ch08/demo/echoargs";
    char *myargs[] = {"echoargs", "This", "is", "a", "demo" };
    
    int
    main(void)
    {
      pid_t pid;
    
      if ((pid = fork()) < 0) {
        printf("fork error: %s
    ", strerror(errno));
        exit(-1);
      } else if (pid == 0) {
        printf("Transmits arguments by vector
    ");
        if (execv(echoargsPath, myargs) < 0) {
          printf("execv error: %s
    ", strerror(errno));
          exit(-1);
        }
      }
      wait(NULL);
    
      if ((pid = fork()) < 0) {
        printf("fork error: %s
    ", strerror(errno));
        exit(-1);
      } else if (pid == 0) {
        printf("Transmits arguments by list
    ");
        if (execl(echoargsPath, myargs[0], myargs[1],
                  myargs[2], myargs[3], "another", myargs[4], (char *)0) < 0) {
          printf("execv error: %s
    ", strerror(errno));
          exit(-1);
        }
      }
      wait(NULL);
      
      exit(0);
    }
    View Code

    编译该程序,生成并执行文件 execdemo,

    lienhua34:demo$ gcc -o execdemo execdemo.c
    lienhua34:demo$ ./execdemo
    Transmits arguments by vector
    arg1: This
    arg2: is
    arg3: a
    arg4: demo
    Transmits arguments by list
    arg1: This
    arg2: is
    arg3: a
    arg4: another
    arg5: demo

    观察上面的程序 execdemo.c,我们看到传递的参数表中 myargs[0] 等于“echoargs”。这是因为在 C 语言中 main 函数的命令行参数第一个默认都是要该执行程序的文件名。所以,我们这里调用 exec 函数传递参数表是第一个参数也要设置为要执行的新程序文件名。

    需要特别说明一点,在调用 execl、execle 和 execlp 函数传递参数表,在最后一个命令行参数之后跟着一个空指针。如果要用常量 0 来表示空指针,则必须要将它转换为一个字符指针,否则它默认会是整型参数。如果一个整型数的长度和 char * 的长度不同,那么 exec 函数在执行时的实际参数将会出错。

    2 带有环境变量列表的 exec 函数

    带有 e 结尾的 exec 函数(execle 和 execve)可以传递一个指向环境字符串指针数组的指针。其他四个 exec 函数则使用调用进程中的 environ 变量(关于 environ 变量可以参考文档“进程环境变量”)为新进程复制现有的环境。

    下面我们来看一个例子。我们有一个 echoenv.c 文件,其输出进程的所有环境变量,其代码如下所示,

    #include <stdlib.h>
    #include <stdio.h>
    
    int
    main(void)
    {
      extern char **environ;
      char **ptr;
    
      for (ptr = environ; *ptr != NULL; ptr++) {
        printf("%s
    ", *ptr);
      }
      exit(0);
    }

    编译该文件,生成并执行 echoenv 文件,

    lienhua34:demo$ gcc -o echoenv echoenv.c
    lienhua34:demo$ ./echoenv
    MANPATH=/usr/local/texlive/2013/texmf-dist/doc/man:
    /usr/local/texlive/2013/texmf-dist/doc/man:
    SSH_AGENT_PID=1693
    ...
    XAUTHORITY=/home/lienhua34/.Xauthority
    _=./echoenv

    下面 execedemo.c 文件中,我们分别使用 execv 函数和 execve 函数调用 echoenv 文件,然后查看两者打印的环境变量列表有什么区别。

    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <errno.h>
    #include <string.h>
    #include <sys/wait.h>
    
    char echoenvPath[] = "/home/lienhua34/program/c/apue/ch08/demo/echoenv";
    char *myEnvs[] = { "env1=foo", "env2=bar" };
    
    int
    main(void)
    {
      pid_t pid;
    
      if (putenv("newenv=add in parent process") < 0) {
        printf("putenv error: %s
    ", strerror(errno));
        exit(-1);
      }
      
      if ((pid = fork()) < 0) {
        printf("fork error: %s
    ", strerror(errno));
        exit(-1);
      } else if (pid == 0) {
        printf("Call echoenv by execv
    ");
        if (execv(echoenvPath, NULL) < 0) {
          printf("execv error: %s
    ", strerror(errno));
          exit(-1);
        }
      }
      wait(NULL);
    
      if ((pid = fork()) < 0) {
        printf("fork error: %s
    ", strerror(errno));
        exit(-1);
      } else if (pid == 0) {
        printf("
    Call echoenv by execve
    ");
        if (execve(echoenvPath, NULL, myEnvs) < 0) {
          printf("execve error: %s
    ", strerror(errno));
          exit(-1);
        }
      }
      wait(NULL);
      
      exit(0);
    }
    execedemo.c

    编译该程序,生成并执行文件 execedemo,

    lienhua34:demo$ gcc -o execedemo execedemo.c
    lienhua34:demo$ ./execedemo
    Call echoenv by execv
    MANPATH=/usr/local/texlive/2013/texmf-dist/doc/man:
    /usr/local/texlive/2013/texmf-dist/doc/man:
    SSH_AGENT_PID=1693
    ...
    XAUTHORITY=/home/lienhua34/.Xauthority
    _=./execedemo
    newenv=add in parent process
    Call echoenv by execve
    env1=foo
    env2=bar

    从上面的运行结果,我们看到调用 execv 函数时父进程会将其设置的环境变量(newenv=add in parent process)也传递给了子进程。而调用execve 函数时,子进程的环境变量列表只有 execve 函数传递的 myEnvs 列表。

    3 取文件名作为参数的 exec 函数

    含有 p 作为结尾的两个 exec 函数(execlp 和 execvp)传递的是新程序的文件名,而其它四个传递的是路径名。这两个 exec 函数传递的 filename参数按照 PATH 环境变量,在指定的各个目录中寻找可执行文件。

    PATH 环境变量包含一张目录表(称为路径前缀),目录之间用冒号(:)分割。例如,
        PATH=/bin:/usr/bin:/usr/local/bin:.
    指定了四个目录项,最后一个路径前缀是当前目录。(零长前缀也表示当前目录,在 value 的开始处可用: 表示,在行中间则要用:: 表示,在行尾则以:表示。)

    我们来看一个例子。我们有一个 execvpdemo.c 文件,在该文件的目录下有一个 echoargs 可执行文件,该执行文件输出命令行参数。

    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <errno.h>
    #include <string.h>
    #include <sys/wait.h>
    char *myargs[] = {"echoargs", "hello" };
    int
    main(void)
    {
      pid_t pid;
      char *pathEnvVal;
      
      if ((pathEnvVal = getenv("PATH")) == NULL) {
        printf("putenv error: %s
    ", strerror(errno));
        exit(-1);
      }
      printf("PATH=%s
    ", pathEnvVal);
      
      if ((pid = fork()) < 0) {
        printf("fork error: %s
    ", strerror(errno));
        exit(-1);
      } else if (pid == 0) {
        if (execvp("echoargs", myargs) < 0) {
          printf("execve error: %s
    ", strerror(errno));
          exit(-1);
        }
      }
      wait(NULL);
    
      if (setenv("PATH", "/usr/bin:.", 1) < 0) {
        printf("setenv error: %s
    ", strerror(errno));
        exit(-1);
      }
      printf("PATH=/usr/bin:.
    ");
      
      if ((pid = fork()) < 0) {
        printf("fork error: %s
    ", strerror(errno));
        exit(-1);
      } else if (pid == 0) {
        if (execvp("echoargs", myargs) < 0) {
          printf("execve error: %s
    ", strerror(errno));
          exit(-1);
        }
      }
      wait(NULL);
      
      exit(0);
    }
    execvpdemo.c

    编译该程序,生成并执行文件 execvpdemo,

    lienhua34:demo$ gcc -o execvpdemo execvpdemo.c
    lienhua34:demo$ ./execvpdemo
    PATH=/usr/local/texlive/2013/bin/i386-linux:/usr/local/texlive/2013/bin/i386-linux:/usr/lib/lightdm/lightdm:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games
    execve error: No such file or directory
    PATH=/usr/bin:.
    arg1: hello

    从上面的运行结果,我们看到在原始的 PATH 环境变量中没有包含当前目录,于是调用 execvp 函数失败,提示找不到 echoargs 文件。而当将PATH 环境变量设置为“/usr/bin:.”之后,调用 execvp 函数能够正常执行echoargs 文件。

    4 exec 函数的其他性质

    在调用 exec 函数新程序之后,新进程会保留 exec 之前进程的大多数特征。但是,有几个需要特殊说明一下:有效用户 ID、有效组 ID 和文件描述符。

    在执行 exec 前后进程的实际用户 ID 和实际组 ID 保持不变,而有效用户 ID 和有效组 ID 是否改变跟要执行的程序文件的设置用户 ID 和设置组 ID 位有关。如果要执行的程序文件的设置用户 ID 位没有设置,则有效用户 ID 不变;否则,执行 exec 之后,进程的有效用户 ID 将被设置为要执行的程序文件的所有者。(对于有效组 ID 的情况类似。)

    在文档“fcntl 函数访问已打开文件的性质”中,我们提到打开的文件描述符都有一个执行时关闭标志(close-on-exec),该标志跟调用 exec 时是否要关闭该文件描述符有关。若某个打开的文件描述符设置了执行时关闭标志,则在执行 exec 函数时会将该描述符关闭,否则该描述符保持不变。文件描述符的执行时关闭标志默认是不设置的。

    POSIX.1 明确要求在执行 exec 时关闭打开的目录流。这通常是由opendir 函数实现的,它调用 fcntl 函数为对应打开目录流的文件描述符设置执行时关闭标志。

    (done)

  • 相关阅读:
    Codeforces 525C Om Nom and Candies 枚举 + 复杂度分析
    Codeforces 526B Om Nom and Dark Park 树形dp
    Codeforces 526A King of Thieves 枚举
    Regionals 2014 Asia
    Regionals 2014 Asia
    access数据库和sqlsever数据库sql语句的布尔值boolean的写法
    取消阴影行
    引用其他单元的2种方法
    选中阴影行
    全选
  • 原文地址:https://www.cnblogs.com/lienhua34/p/4009368.html
Copyright © 2011-2022 走看看