用fork函數創建子進程后,子進程往往要調用一種exec函數以執行另一個程序。當進程調用一種exec函數時,該進程執行的程序完全替換為新程序,而新程序則從其main函數開始執行。因為調用exec并不創建新進程,所以前后的進程ID并未改變。exec只是用一個全新的程序替換了當前進程的正文、數據、堆和棧段。
有6種不同的exec函數可供使用,它們常常被統稱為exec函數。這些exec函數使得UNIX進程控制原語更加完善。用fork可以創建新進程,用exec可以執行新程序。exit函數和兩個wait函數處理終止和等待終止。這些是我們需要的基本的進程控制原語。
#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( const char *filename, char *const argv[] );6個函數返回值:若出錯則返回-1,若成功則不返回值
這些函數之間的第一個區別是前4個取路徑名作為參數,后兩個(execlp和execvp,p表示PATH,個人理解)則取文件名作為參數。當指定filename作為參數時:
PATH變量包含了一張目錄表(稱為路徑前綴),目錄之間用冒號(:)分隔。例如name = value環境字符串:
PATH=/bin:/usr/bin:/usr/local/bin/:.
指定在4個目錄中進行搜索。最后的路徑前綴(.)表示當前目錄。(零長前綴也表示當前目錄。在value的開始處可用:表示,在行中間則要用::表示,在行尾則以:表示。)
如果execlp或execvp使用路徑名前綴中的一個找到了一個可執行文件,但是該文件不是由連接編輯器產生的機器可執行文件,則認為該文件是一個shell腳本,于是試著調用/bin/sh,并以該filename作為shell的輸入。
第二個區別與參數表的傳遞有關(l表示list,v表示矢量vector)。函數execl、execlp和execle要求將新程序的每個命令行參數都說明為一個單獨的參數。這種參數表以空指針結尾。對于另外三個函數(execv、execvp和execve),則應先構造一個指向各參數的指針數組,然后將該數組地址作為這三個函數的參數。
在ISO C原型之前,對execl、execle和execlp這三個函數表示命令行參數的一般方法是:
char *arg0, char *arg1, …, char *argn, (char *)0
應當特別指出的是:在最后一個命令行參數之后跟了一個空指針。如果用常數0來表示一個空指針,則必須將它強制轉換為一個字符指針,否則將它解釋為整型參數。如果一個整型數的長度與char *的長度不同,那么exec函數的實際參數就將出錯。
最后一個區別與向新程序傳遞環境表相關。以e結尾的兩個函數(execle和execve)可以傳遞一個指向環境字符串指針數組的指針。其他四個函數則使用調用進程中的environ變量為新程序復制現有的環境。通常,一個進程允許將其環境傳播給其子進程,但有時也有這種情況,即進程想要為子進程指定某一個確定的環境。
在使用ISO C原型之前,execle的參數是:
char *pathname, char *arg0, …, char *argn, (char *)0, char *envp[]
從中可見,最后一個參數是指向環境字符串的各字符指針構成的數組的地址。而ISO C原型中,所有命令行參數、空指針和envp指針都用省略號(...)表示。
這6個exec函數名中的字符說明:字母p表示該函數取filename作為參數,并且用PATH環境變量尋找可執行文件。字母l表示該函數取一個參數表,它與字母v互斥。字母v表示該函數取一個argv[]矢量。字母e表示該函數取envp[]數組,而不使用當前環境。
表8-6 6個exec函數之間的區別
在執行exec后,進程ID沒有改變。除此之外,執行新程序的進程還保持了原進程的下列特征:
對打開文件的處理與每個描述符的執行時關閉(close-on-exec)標志值有關。進程中每個打開描述符都有一個執行時關閉標志。若此標志設置,則在執行exec時關閉該描述符,否則該描述符仍打開。除非特地用fcntl設置了該標志,否則系統默認操作是在執行exec后仍保持這種描述符打開。
POSIX.1明確要求在執行exec時關閉打開的目錄流。這通常是opendir函數實現的,它調用fcntl函數為對應于打開目錄流的描述符設置執行時關閉標志。
注意,在執行exec前后實際用戶ID和實際組ID保持不變,而有效ID是否改變則取決于所執行程序文件的設置用戶ID位和設置組ID位是否設置。如果新程序的設置用戶ID位已設置,則有效用戶ID變成程序文件所有者的ID,否則有效用戶ID不變。對組ID的處理方式與此相同。
在很多UNIX實現中,這6個函數中只有execve是內核的系統調用。另外5個只是庫函數,它們最終都要調用該系統調用。
程序清單8-8 exec函數實例
[root@localhost apue]# cat PRog8-8.c#include "apue.h"#include <sys/wait.h>char *env_init[] = { "USER=unknown", "PATH=/tmp", NULL };intmain(void){ pid_t pid; if((pid = fork()) < 0) { err_sys("fork error"); } else if(pid == 0) /*specify pathname, specify environment */ { if(execle("/home/zhu/apue/echoall", "echoall", "myarg1", "MY ARG2", (char *)0, env_init) < 0) err_sys("execle error"); } if(waitpid(pid, NULL, 0) < 0) err_sys("wait error"); if((pid = fork()) < 0) { err_sys("fork error"); } else if(pid == 0) /* specify filename, inherit environment */ { if(execlp("echoall", "echoall", "only 1 arg", (char *)0) < 0) err_sys("execlp error"); } exit(0);}
注意,我們將第一個參數(新程序中的argv[0])設置為路徑名的文件名分量。某些shell將此參數設置為完整的路徑名。這只是一個慣例。我們可將argv[0]設置為任何字符串。
程序清單8-8中要執行的程序echoall示于程序清單8-9中。這是一個很普通的程序,它回送其所有命令行參數及全部環境表。
程序清單8-9 回送所用命令行參數和所有環境字符串
[root@localhost apue]# cat echoall.c#include "apue.h"intmain(int argc, char *argv[]){ int i; char **ptr; extern char **environ; for(i=0; i<argc; i++) /* echo all command-line args */ printf("argv[%d]: %s/n", i, argv[i]); for(ptr = environ; *ptr != 0; ptr++) /*and all env strings */ printf("%s/n", *ptr); exit(0);}
執行程序清單8-8得到
[root@localhost apue]# ./prog8-8argv[0]: echoallargv[1]: myarg1argv[2]: MY ARG2USER=unknownPATH=/tmpargv[0]: echoallargv[1]: only 1 argSSH_AGENT_PID=4279HOSTNAME=localhost.localdomainDESKTOP_STARTUP_ID=SHELL=/bin/bash......
最后總結一下使用exec函數時需要考慮哪些參數:首先,要執行的新程序名(帶不帶路徑);其次,命令行參數(列表形式還是指針數組形式);最后,環境表(要不要傳遞環境表)
本篇博文內容摘自《UNIX環境高級編程》(第二版),僅作個人學習記錄所用。關于本書可參考:http://www.apuebook.com/。
新聞熱點
疑難解答