exec函式族
也許有不少讀者從本系列文章一推出就開始讀,一直到這裡還有一個很大的疑惑:既然所有新程式都是由fork產生的,而且由fork產生的子程式和父程式幾乎完全一樣,那豈不是意味著系統中所有的程式都應該一模一樣了嗎?而且,就我們的常識來說,當我們執行一個程式的時候,新產生的程式的內容應就是程式的內容才對。是我們理解錯了嗎?顯然不是,要解決這些疑惑,就必須提到我們下面要介紹的exec系統呼叫。
說是exec系統呼叫,實際上在Linux中,並不存在一個exec()的函式形式,exec指的是一組函式,一共有6個,分別是:
#include <unistd.h> 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 *const argv[]); int execvp(const char *file, char *const argv[]); int execve(const char *path, char *const argv[], char *const envp[]); |
其中只有execve是真正意義上的系統呼叫,其它都是在此基礎上經過包裝的庫函式。
exec函式族的作用是根據指定的檔名找到可執行檔案,並用它來取代呼叫程式的內容,換句話說,就是在呼叫程式內部執行一個可執行檔案。這裡的可執行檔案既可以是二進位制檔案,也可以是任何Linux下可執行的指令碼檔案。
與一般情況不同,exec函式族的函式執行成功後不會返回,因為呼叫程式的實體,包括程式碼段,資料段和堆疊等都已經被新的內容取代,只留下程式ID等一些表面上的資訊仍保持原樣,頗有些神似"三十六計"中的"金蟬脫殼"。看上去還是舊的軀殼,卻已經注入了新的靈魂。只有呼叫失敗了,它們才會返回一個-1,從原程式的呼叫點接著往下執行。
現在我們應該明白了,Linux下是如何執行新程式的,每當有程式認為自己不能為系統和擁護做出任何貢獻了,他就可以發揮最後一點餘熱,呼叫任何一個exec,讓自己以新的面貌重生;或者,更普遍的情況是,如果一個程式想執行另一個程式,它就可以fork出一個新程式,然後呼叫任何一個exec,這樣看起來就好像通過執行應用程式而產生了一個新程式一樣。
事實上第二種情況被應用得如此普遍,以至於Linux專門為其作了優化,我們已經知道,fork會將呼叫程式的所有內容原封不動的拷貝到新產生的子程式中去,這些拷貝的動作很消耗時間,而如果fork完之後我們馬上就呼叫exec,這些辛辛苦苦拷貝來的東西又會被立刻抹掉,這看起來非常不划算,於是人們設計了一種"寫時拷貝(copy-on-write)"技術,使得fork結束後並不立刻複製父程式的內容,而是到了真正實用的時候才複製,這樣如果下一條語句是exec,它就不會白白作無用功了,也就提高了效率。
上面6條函式看起來似乎很複雜,但實際上無論是作用還是用法都非常相似,只有很微小的差別。在學習它們之前,先來了解一下我們習以為常的main函式。
下面這個main函式的形式可能有些出乎我們的意料:
int main(int argc, char *argv[], char *envp[]) |
它可能與絕大多數教科書上描述的都不一樣,但實際上,這才是main函式真正完整的形式。
引數argc指出了執行該程式時命令列引數的個數,陣列argv存放了所有的命令列引數,陣列envp存放了所有的環境變數。環境變數指的是一組值,從使用者登入後就一直存在,很多應用程式需要依靠它來確定系統的一些細節,我們最常見的環境變數是PATH,它指出了應到哪裡去搜尋應用程式,如/bin;HOME也是比較常見的環境變數,它指出了我們在系統中的個人目錄。環境變數一般以字串"XXX=xxx"的形式存在,XXX表示變數名,xxx表示變數的值。
值得一提的是,argv陣列和envp陣列存放的都是指向字串的指標,這兩個陣列都以一個NULL元素表示陣列的結尾。
我們可以通過以下這個程式來觀看傳到argc、argv和envp裡的都是什麼東西:
/* main.c */ int main(int argc, char *argv[], char *envp[]) { printf("\n### ARGC ###\n%d\n", argc); printf("\n### ARGV ###\n"); while(*argv) printf("%s\n", *(argv++)); printf("\n### ENVP ###\n"); while(*envp) printf("%s\n", *(envp++)); return 0; } |
編譯它:
$ cc main.c -o main |
執行時,我們故意加幾個沒有任何作用的命令列引數:
$ ./main -xx 000 ### ARGC ### 3 ### ARGV ### ./main -xx 000 ### ENVP ### PWD=/home/lei REMOTEHOST=dt.laser.com HOSTNAME=localhost.localdomain QTDIR=/usr/lib/qt-2.3.1 LESSOPEN=|/usr/bin/lesspipe.sh %s KDEDIR=/usr USER=lei LS_COLORS= MACHTYPE=i386-redhat-linux-gnu MAIL=/var/spool/mail/lei INPUTRC=/etc/inputrc LANG=en_US LOGNAME=lei SHLVL=1 SHELL=/bin/bash HOSTTYPE=i386 OSTYPE=linux-gnu HISTSIZE=1000 TERM=ansi HOME=/home/lei PATH=/usr/local/bin:/bin:/usr/bin:/usr/X11R6/bin:/home/lei/bin _=./main |
我們看到,程式將"./main"作為第1個命令列引數,所以我們一共有3個命令列引數。這可能與大家平時習慣的說法有些不同,小心不要搞錯了。
現在回過頭來看一下exec函式族,先把注意力集中在execve上:
int execve(const char *path, char *const argv[], char *const envp[]); |
對比一下main函式的完整形式,看出問題了嗎?是的,這兩個函式裡的argv和envp是完全一一對應的關係。execve第1個引數path是被執行應用程式的完整路徑,第2個引數argv就是傳給被執行應用程式的命令列引數,第3個引數envp是傳給被執行應用程式的環境變數。
留心看一下這6個函式還可以發現,前3個函式都是以execl開頭的,後3個都是以execv開頭的,它們的區別在於,execv開頭的函式是以"char *argv[]"這樣的形式傳遞命令列引數,而execl開頭的函式採用了我們更容易習慣的方式,把引數一個一個列出來,然後以一個NULL表示結束。這裡的NULL的作用和argv陣列裡的NULL作用是一樣的。
在全部6個函式中,只有execle和execve使用了char *envp[]傳遞環境變數,其它的4個函式都沒有這個引數,這並不意味著它們不傳遞環境變數,這4個函式將把預設的環境變數不做任何修改地傳給被執行的應用程式。而execle和execve會用指定的環境變數去替代預設的那些。
還有2個以p結尾的函式execlp和execvp,咋看起來,它們和execl與execv的差別很小,事實也確是如此,除execlp和execvp之外的4個函式都要求,它們的第1個引數path必須是一個完整的路徑,如"/bin/ls";而execlp和execvp的第1個引數file可以簡單到僅僅是一個檔名,如"ls",這兩個函式可以自動到環境變數PATH制定的目錄裡去尋找。
知識介紹得差不多了,接下來我們看看實際的應用:
/* exec.c */ #include <unistd.h> main() { char *envp[]={"PATH=/tmp", "USER=lei", "STATUS=testing", 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 by execl", NULL)<0) perror("Err on execl"); if(fork()==0) if(execlp("echo", "echo", "executed by execlp", 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"); } |
程式裡呼叫了2個Linux常用的系統命令,echo和env。echo會把後面跟的命令列引數原封不動的列印出來,env用來列出所有環境變數。
由於各個子程式執行的順序無法控制,所以有可能出現一個比較混亂的輸出--各子程式列印的結果交雜在一起,而不是嚴格按照程式中列出的次序。
編譯並執行:
$ cc exec.c -o exec $ ./exec executed by execl PATH=/tmp USER=lei STATUS=testing executed by execlp excuted by execv executed by execvp PATH=/tmp USER=lei STATUS=testing |
果然不出所料,execle輸出的結果跑到了execlp前面。
大家在平時的程式設計中,如果用到了exec函式族,一定記得要加錯誤判斷語句。因為與其他系統呼叫比起來,exec很容易受傷,被執行檔案的位置,許可權等很多因素都能導致該呼叫的失敗。最常見的錯誤是:
- 找不到檔案或路徑,此時errno被設定為ENOENT;
- 陣列argv和envp忘記用NULL結束,此時errno被設定為EFAULT;
- 沒有對要執行檔案的執行許可權,此時errno被設定為EACCES。
原文出自:http://www-128.ibm.com/developerworks/cn/linux/kernel/syscall/part3/index.html
相關文章
- exec()函式函式
- exec函式簇函式
- Linux系統程式設計之程式替換:exec 函式族Linux程式設計函式
- python函式每日一講 - exec執行函式Python函式
- 正規表示式match()函式和exec()函式的區別函式
- python內建函式-eval()函式與exec()函式的區別Python函式
- PHP exec system passthru系統函式PHP函式
- 如何使用python中的exec函式?Python函式
- 伺服器不支援:curl_exec函式伺服器函式
- 正規表示式exec()函式只有第一執行有效分析函式
- Linux系統程式設計(9)—— 程式之程式控制函式exec系列函式Linux程式設計函式
- PHP4使用者手冊:函式->CURL->curl_exec (轉)PHP函式
- 多程式函式系列fork(), wait(), exec()系列,system(), posix_spawn()例項詳解函式AI
- 檔案和目錄之stat族函式——APUE學習筆記(2)函式筆記
- linux系統程式設計之程式(五):exec系列函式(execl,execlp,execle,execv,execvp)使用Linux程式設計函式
- MySQL函式大全(字串函式,數學函式,日期函式,系統級函式,聚合函式)MySql函式字串
- Oracle 函式大全(字串函式,數學函式,日期函式,邏輯運算函式,其他函式)Oracle函式字串
- 【函式式 Swift】函式式思想函式Swift
- python中id()函式、zip()函式、map()函式、lamda函式Python函式
- 【函式】Oracle函式系列(2)--數學函式及日期函式函式Oracle
- Python 擴充之特殊函式(lambda 函式,map 函式,filter 函式,reduce 函式)Python函式Filter
- 第7章 IF函式 COUNTIF函式 SUMIF函式函式
- 字元函式、數字函式和日期函式字元函式
- 【函式】Oracle EXTRACT()函式與to_char() 函式函式Oracle
- MySQL(四)日期函式 NULL函式 字串函式MySql函式Null字串
- 【函式】ORACLE函式大全函式Oracle
- PHP-fpm Linux 環境使用 exec 函式呼叫 FFmpeg,報錯 FFmpeg: command not found 的解決方法PHPLinux函式
- (譯) 函式式 JS #2: 函式!函式JS
- python execPython
- 核函式 多項式核函式 高斯核函式(常用)函式
- 函式名/函式地址/函式指標函式指標
- 第 8 節:函式-匿名函式、遞迴函式函式遞迴
- sqlplus"strace: exec: Exec format error"故障處理SQLORMError
- lambda匿名函式sorted排序函式filter過濾函式map對映函式函式排序Filter
- js函式 函式自呼叫 返回函式的函式 (閉包)JS函式
- main函式的入口函式AI函式
- (函式)實現strstr函式函式
- 字串函式之Strtok()函式字串函式