Linux系統程式設計(9)—— 程式之程式控制函式exec系列函式
在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執行的程式將按序執行。
相關文章
- Linux系統程式設計之程式替換:exec 函式族Linux程式設計函式
- linux系統時間程式設計(9) 計算程式片段執行時間clock函式Linux程式設計函式
- 函式程式設計函式程式設計
- 【Linux網路程式設計】Socket Api函式Linux程式設計API函式
- select函式socket程式設計函式程式設計
- JS 命令式 宣告式 函式式 程式設計?JS函式程式設計
- 好程式設計師Python教程系列遞迴函式與匿名函式呼叫程式設計師Python遞迴函式
- python函式程式設計 返回函式 匿名函式 裝飾器 偏函式Python函式程式設計
- 好程式設計師Python培訓分享函數語言程式設計之匿名函式程式設計師Python函數函式
- JavaScript函數語言程式設計之pointfree與宣告式程式設計JavaScript函數程式設計
- 函式響應式程式設計與RxSwift函式程式設計Swift
- C程式函式呼叫&系統呼叫C程式函式
- Bash程式設計007——函式(一)程式設計函式
- python程式設計之slice與indices函式用法Python程式設計函式
- PHP exec system passthru系統函式PHP函式
- 不用任何賦值的程式設計稱為*函式式*程式設計賦值程式設計函式
- 揚帆起航:從指令式程式設計到函式響應式程式設計程式設計函式
- 函數語言程式設計-鏈式程式設計RAC函數程式設計
- 瞭解 JavaScript 函數語言程式設計 - 宣告式函式JavaScript函數程式設計函式
- JavaScript函數語言程式設計,真香之組合函式(二)JavaScript函數程式設計函式
- JavaScript函數語言程式設計之深入理解純函式JavaScript函數程式設計函式
- Python函數語言程式設計-高階函式、匿名函式、裝飾器、偏函式Python函數程式設計函式
- 03 shell程式設計之case語句與函式程式設計函式
- Ardunio和HAL庫函式程式設計函式程式設計
- Python 函數語言程式設計 – 高階函式Python函數程式設計函式
- Python函數語言程式設計自帶函式Python函數程式設計函式
- Day 14 匿名函式 內建函式 物件導向程式設計函式物件程式設計
- Lambda表示式入門--函數語言程式設計與函式式介面函數程式設計函式
- 深入理解javascript系列(十三):函式與函數語言程式設計(2)JavaScript函式函數程式設計
- 深入理解javascript系列(十二):函式與函數語言程式設計(1)JavaScript函式函數程式設計
- linux系統時間程式設計(6) 日曆時間tm轉字串strftime函式Linux程式設計字串函式
- python函數語言程式設計之yield表示式形式Python函數程式設計
- 【多程式】Linux中fork()函式詳解|多程式Linux函式
- Linux系統程式設計之程式控制(程式建立、終止、等待及替換)Linux程式設計
- Java程式設計基礎05——方法(函式)Java程式設計函式
- 前端-JavaScript非同步程式設計async函式前端JavaScript非同步程式設計函式
- 函數語言程式設計:Lambda 表示式函數程式設計
- .NET併發程式設計-函式閉包程式設計函式
- Python基礎程式設計(十六)——函式4Python程式設計函式