轉自:http://www.cppblog.com/prayer/archive/2009/04/15/80077.htmlhtml
也許有很多讀者從本系列文章一推出就開始讀,一直到這裏還有一個很大的疑惑:既然全部新進程都是由fork產生的,並且由fork產生的子進程和父進程幾乎徹底同樣,那豈不是意味着系統中全部的進程都應該如出一轍了嗎?並且,就咱們的常識來講,當咱們執行一個程序的時候,新產生的進程的內容應就是程序的內容纔對。是咱們理解錯了嗎?顯然不是,要解決這些疑惑,就必須提到咱們下面要介紹的exec系統調用。linux
說是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是真正意義上的系統調用,其它都是在此基礎上通過包裝的庫函數。bash
exec函數族的做用是根據指定的文件名找到可執行文件,並用它來取代調用進程的內容,換句話說,就是在調用進程內部執行一個可執行文件。這裏的可執行文件既能夠是二進制文件,也能夠是任何Linux下可執行的腳本文件。less
與通常狀況不一樣,exec函數族的函數執行成功後不會返回,由於調用進程的實體,包括代碼段,數據段和堆棧等都已經被新的內容取代,只留下進程ID等一些表面上的信息仍保持原樣,很有些神似"三十六計"中的"金蟬脫殼"。看上去仍是舊的軀殼,卻已經注入了新的靈魂。只有調用失敗了,它們纔會返回一個-1,從原程序的調用點接着往下執行。dom
如今咱們應該明白了,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很容易受傷,被執行文件的位置,權限等不少因素都能致使該調用的失敗。最多見的錯誤是: