Linux系統程式設計(9)—— 程式之程式控制函式exec系列函式

尹成發表於2014-07-25

 

在Linux中,並不存在exec()函式,exec指的是一組函式,一共有6個,分別是:

#include <unistd.h>
extern char **environ;
int execl(const char *path, const char*arg, ...);
int execlp(const char *file, const char*arg, ...);
int execle(const char *path, const char*arg, ..., char * const envp[]);
int execv(const char *path, char *constargv[]);
int execvp(const char *file, char *constargv[]);
int execve(const char *path, char *constargv[], char *const envp[]);

 

其中只有execve是真正意義上的系統呼叫,其它都是在此基礎上經過包裝的庫函式。

exec函式族的作用是根據指定的檔名找到可執行檔案,並用它來取代呼叫程式的內容,換句話說,就是在呼叫程式內部執行一個可執行檔案。這裡的可執行檔案既可以是二進位制檔案,也可以是任何Linux下可執行的指令碼檔案。

當fork建立新程式時,即可使用exec函式執行新的程式,但並不是建立新的程式。

 

1、execl()

int execl(const char *path, const char*argv.......);


函式執行成功則不返回,否則返回-1

功能:execl()用於執行引數path字串代表的檔案路徑,接下來引數代表執行檔案時傳遞argv, 最後一個引數必須以空指標結束。

2、execle()

int execle(const char *path, const char*argv.....,const char *envp[])


功能:執行那個引數path字元代表的檔案路徑,接下來引數代表執行檔案時傳遞的引數argv,最後一個引數必須指向一個新的環境變數陣列,成為新執行程式的環境變數。

3、execlp()

int execlp(const char *path, const char*arg......)


功能:從path環境變數所指目錄中查詢符合引數file的檔名,找到後執行該檔案,接下來引數代表執行檔案時傳遞的argv[0],最後一個引數必須以空指標NULL。

4、execv()

int execv(const char *path, const char*arg[])


功能:執行引數path字元代表的檔案路徑,第二引數以陣列指標來傳遞給執行檔案。

5、execve()

int execve(const char *filename, const char*argv[], const char *envp[])


功能:執行filename字串代表的檔案路徑,中間引數利用陣列指標來傳遞給執行檔案,最後一個引數為傳遞給執行檔案的新環境變數陣列。

6、execvp()

int execvp(const char *filename, const char*argv[])


功能:從path環境變數所指定目錄中查詢符合引數file的檔名, 找到後執行此檔案,第二個引數argv傳遞給要執行的檔案

 

這6個函式都是以exec開頭(表示屬於exec函式組),前3個函式接著字母l的,後3個接著字母v的,我的理解是l表示list(列舉引數),v表示vector(引數向量表)

。它們的區別在於,execv開頭的函式是以"char *argv[]"(vector)形式傳遞命令列引數,而execl開頭的函式採用了羅列(list)的方式,把引數一個一個列出來,然後以一個NULL表示結束。這裡的NULL的作用和argv陣列裡的NULL作用是一樣的。

字母p是指在環境變數PATH的目錄裡去查詢要執行的可執行檔案。2個以p結尾的函式execlp和execvp,看起來,和execl與execv的差別很小,事實也如此,它們的區別從第一個引數名可以看出:除 execlp和execvp之外的4個函式都要求,它們的第1個引數path必須是一個完整的路徑,如"/bin/ls";而execlp和execvp 的第1個引數file可以僅僅只是一個檔名,如"ls",這兩個函式可以自動到環境變數PATH指定的目錄裡去查詢。

字母e是指給可執行檔案指定環境變數。在全部6個函式中,只有execle和execve使用了char *envp[]傳遞環境變數,其它的4個函式都沒有這個引數,這並不意味著它們不傳遞環境變數,這4個函式將把預設的環境變數不做任何修改地傳給被執行的應用程式。而execle和execve用指定的環境變數去替代預設的那些。

返回值

與一般情況不同,exec函式族的函式執行成功後不會返回,因為呼叫程式的實體,包括程式碼段,資料段和堆疊等都已經被新的內容取代,只有程式ID等一些表面上的資訊仍保持原樣。呼叫失敗時,會設定errno並返回-1,然後從原程式的呼叫點接著往下執行。

與其他系統呼叫比起來,exec很容易失敗,被執行檔案的位置,許可權等很多因素都能導致呼叫失敗。因此,使用exec函式族時,一定要加錯誤判斷語句。最常見的錯誤:

找不到檔案或路徑,此時errno被設定為ENOENT;

陣列argv和envp忘記用NULL結束,此時errno被設定為EFAULT;

沒有對要執行檔案的執行許可權,此時errno被設定為EACCES。

 

為此,Linux還專門對fork作了優化:通常fork會將呼叫程式的所有內容原封不動的拷貝到新產生的子程式中去,這些拷貝的動作很消耗時間,而如果fork完之後我們馬上就呼叫exec,那這些辛辛苦苦拷貝來的東西就會被立刻抹掉,這看起來非常不划算,於是人們設計了一種"寫時複製(copy-on-write)" 技術,使得fork結束後並不立刻複製父程式的內容到子程式,而是到了真正使用時才複製,這樣如果下一條語句是exec,它就不會作無用功了。其實"寫時複製"還是有複製,程式的mm結構、頁表都還是被複制了("寫時複製"也必須由這些資訊來支撐。否則核心捕捉到CPU訪存異常,怎麼區分這是“寫時複製”引起的,還是真正的越權訪問呢?)。

而vfork就把事情做絕了,所有有關於記憶體的東西都不復制了,父子程式的記憶體是完全共享的。但是這樣一來又有問題了,雖然使用者程式可以設計很多方法來避免父子程式間的訪存衝突。但是關鍵的一點,父子程式共用著棧,這可不由使用者程式控制的。一個程式進行了關於函式呼叫或返回的操作,則另一個程式的呼叫棧(實際上就是同一個棧)也被影響了。這樣的程式沒法執行下去。所以,vfork有個限制,子程式生成後,父程式在vfork中被核心掛起,直到子程式有了自己的記憶體空間(exec**)或退出(_exit)。並且,在此之前,子程式不能從呼叫vfork的函式中返回(同時,不能修改棧上變數、不能繼續呼叫除_exit或exec系列之外的函式,否則父程式的資料可能被改寫)。

儘管限制很多,vfork後馬上exec效率會比fork高不少。

下面是一個例子:

#include <unistd.h>
int main(void)
{
   char *envp[] = {"PATH=/tmp", "USER=root","STATUS=test", NULL};
   char *argv_execv[] = {"echo", "excuted by execv",NULL};
   char *argv_execvp[] = {"echo", "executed by execvp",NULL};
   char *argv_execve[] = {"env", NULL};
 
   if (fork() == 0)
       if (execl("/bin/echo", "echo", "executed byexecl", NULL) < 0)
           perror("Err on execl");
 
   if (fork() == 0)
       if (execlp("echo", "echo", "executed byexeclp", NULL) < 0)
           perror("Err on execlp");
 
   if (fork() == 0)
       if (execle("/usr/bin/env", "env", NULL, envp) <0)
           perror("Err on execle");
 
   if (fork() == 0)
       if (execv("/bin/echo", argv_execv) < 0)
           perror("Err on execv");
 
   if (fork() == 0)
       if (execvp("echo", argv_execvp) < 0)
           perror("Err on execvp");
 
   if (fork() == 0)
       if (execve("/usr/bin/env", argv_execve, envp) < 0)
           perror("Err on execve");
}

由於各個子程式執行的順序無法控制,所以有可能出現一個比較混亂的輸出--各子程式列印的結果交雜在一起,而不是嚴格按照程式中列出的次序。若將程式中fork都改為vfork,則各個exec執行的程式將按序執行。

 

 

相關文章