Linux進程式控制制編程之exec函數族

來源:互聯網
上載者:User

     本想自己寫一篇記錄exec函數族的小文章,但是在我學習相關知識的時候,發現網上一篇文章已經對其介紹得比較周詳。故採用他山之石啦。以下將摘錄出自http://www.ibm.com/developerworks/cn/linux/kernel/syscall/part3/index.html其中關於exec函數族部分。

 

 

1.10 exec

也許有不少讀者從本系列文章一推出就開始讀,一直到這裡還有一個很大的疑惑:既然所有新進程都是由fork產生的,而且由fork產生的子進程和父進程幾乎完全一樣,那豈不是意味著系統中所有的進程都應該一模一樣了嗎?而且,就我們的常識來說,當我們執行一個程式的時候,新產生的進程的內容應就是程式的內容才對。是我們理解錯了嗎?顯然不是,要解決這些疑惑,就必須提到我們下面要介紹的exec系統調用。

1.10.1 簡介

說是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,它就不會白白作無用功了,也就提高了效率。

1.10.2 稍稍深入

上面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-xx000### ENVP ###PWD=/home/leiREMOTEHOST=dt.laser.comHOSTNAME=localhost.localdomainQTDIR=/usr/lib/qt-2.3.1LESSOPEN=|/usr/bin/lesspipe.sh %sKDEDIR=/usrUSER=leiLS_COLORS=MACHTYPE=i386-redhat-linux-gnuMAIL=/var/spool/mail/leiINPUTRC=/etc/inputrcLANG=en_USLOGNAME=leiSHLVL=1SHELL=/bin/bashHOSTTYPE=i386OSTYPE=linux-gnuHISTSIZE=1000TERM=ansiHOME=/home/leiPATH=/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制定的目錄裡去尋找。

1.10.3 實戰

知識介紹得差不多了,接下來我們看看實際的應用:

/* 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$ ./execexecuted by execlPATH=/tmpUSER=leiSTATUS=testingexecuted by execlpexcuted by execvexecuted by execvpPATH=/tmpUSER=leiSTATUS=testing

果然不出所料,execle輸出的結果跑到了execlp前面。

大家在平時的編程中,如果用到了exec函數族,一定記得要加錯誤判斷語句。因為與其他系統調用比起來,exec很容易受傷,被執行檔案的位置,許可權等很多因素都能導致該調用的失敗。最常見的錯誤是:

  1. 找不到檔案或路徑,此時errno被設定為ENOENT;
  2. 數組argv和envp忘記用NULL結束,此時errno被設定為EFAULT;
  3. 沒有對要執行檔案的運行許可權,此時errno被設定為EACCES。
相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.