UNIX環境編程學習筆記(20)——進程管理之exec 函數族

lienhua34
2014-10-07html

在文檔「進程控制三部曲」中,咱們提到 fork 函數建立子進程以後,一般都會調用 exec 函數來執行一個新程序。調用 exec 函數以後,該進程就將執行的程序替換爲新的程序,而新的程序則從 main 函數開始執行。linux

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,若成功則沒有返回值ide

可能不少人會以爲這六個函數太難記了。可是,咱們仔細觀察會發現,這六個函數的命名是有一些規律的。函數

• 含有 l 和 v 的 exec 函數的參數表傳遞方式是不一樣的。spa

• 含有 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,其輸出全部的命令行參數。code

#include <stdio.h>
#include <stdlib.h>

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

編譯該程序,生成 echoargs 文件,htm

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\n", strerror(errno));
    exit(-1);
  } else if (pid == 0) {
    printf("Transmits arguments by vector\n");
    if (execv(echoargsPath, myargs) < 0) {
      printf("execv error: %s\n", strerror(errno));
      exit(-1);
    }
  }
  wait(NULL);

  if ((pid = fork()) < 0) {
    printf("fork error: %s\n", strerror(errno));
    exit(-1);
  } else if (pid == 0) {
    printf("Transmits arguments by list\n");
    if (execl(echoargsPath, myargs[0], myargs[1],
              myargs[2], myargs[3], "another", myargs[4], (char *)0) < 0) {
      printf("execv error: %s\n", 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\n", *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\n", strerror(errno));
    exit(-1);
  }
  
  if ((pid = fork()) < 0) {
    printf("fork error: %s\n", strerror(errno));
    exit(-1);
  } else if (pid == 0) {
    printf("Call echoenv by execv\n");
    if (execv(echoenvPath, NULL) < 0) {
      printf("execv error: %s\n", strerror(errno));
      exit(-1);
    }
  }
  wait(NULL);

  if ((pid = fork()) < 0) {
    printf("fork error: %s\n", strerror(errno));
    exit(-1);
  } else if (pid == 0) {
    printf("\nCall echoenv by execve\n");
    if (execve(echoenvPath, NULL, myEnvs) < 0) {
      printf("execve error: %s\n", 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\n", strerror(errno));
    exit(-1);
  }
  printf("PATH=%s\n", pathEnvVal);
  
  if ((pid = fork()) < 0) {
    printf("fork error: %s\n", strerror(errno));
    exit(-1);
  } else if (pid == 0) {
    if (execvp("echoargs", myargs) < 0) {
      printf("execve error: %s\n", strerror(errno));
      exit(-1);
    }
  }
  wait(NULL);

  if (setenv("PATH", "/usr/bin:.", 1) < 0) {
    printf("setenv error: %s\n", strerror(errno));
    exit(-1);
  }
  printf("PATH=/usr/bin:.\n");
  
  if ((pid = fork()) < 0) {
    printf("fork error: %s\n", strerror(errno));
    exit(-1);
  } else if (pid == 0) {
    if (execvp("echoargs", myargs) < 0) {
      printf("execve error: %s\n", 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)

相關文章
相關標籤/搜索