exec函數簇

轉自:http://www.cppblog.com/prayer/archive/2009/04/15/80077.htmlhtml

也許有很多讀者從本系列文章一推出就開始讀,一直到這裏還有一個很大的疑惑:既然全部新進程都是由fork產生的,並且由fork產生的子進程和父進程幾乎徹底同樣,那豈不是意味着系統中全部的進程都應該如出一轍了嗎?並且,就咱們的常識來講,當咱們執行一個程序的時候,新產生的進程的內容應就是程序的內容纔對。是咱們理解錯了嗎?顯然不是,要解決這些疑惑,就必須提到咱們下面要介紹的exec系統調用。linux

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是真正意義上的系統調用,其它都是在此基礎上通過包裝的庫函數。bash

exec函數族的做用是根據指定的文件名找到可執行文件,並用它來取代調用進程的內容,換句話說,就是在調用進程內部執行一個可執行文件。這裏的可執行文件既能夠是二進制文件,也能夠是任何Linux下可執行的腳本文件。less

與通常狀況不一樣,exec函數族的函數執行成功後不會返回,由於調用進程的實體,包括代碼段,數據段和堆棧等都已經被新的內容取代,只留下進程ID等一些表面上的信息仍保持原樣,很有些神似"三十六計"中的"金蟬脫殼"。看上去仍是舊的軀殼,卻已經注入了新的靈魂。只有調用失敗了,它們纔會返回一個-1,從原程序的調用點接着往下執行。dom

如今咱們應該明白了,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
            -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制定的目錄裏去尋找。

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
            $ ./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很容易受傷,被執行文件的位置,權限等不少因素都能致使該調用的失敗。最多見的錯誤是:

    1. 找不到文件或路徑,此時errno被設置爲ENOENT;
    2. 數組argv和envp忘記用NULL結束,此時errno被設置爲EFAULT;
    3. 沒有對要執行文件的運行權限,此時errno被設置爲EACCES。
相關文章
相關標籤/搜索