• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>

            Prayer

            在一般中尋求卓越
            posts - 1256, comments - 190, trackbacks - 0, articles - 0
              C++博客 :: 首頁 :: 新隨筆 :: 聯(lián)系 :: 聚合  :: 管理

            exec函數(shù)族

            Posted on 2009-04-15 23:01 Prayer 閱讀(3474) 評(píng)論(0)  編輯 收藏 引用 所屬分類: LINUX/UNIX/AIX

            也許有不少讀者從本系列文章一推出就開始讀,一直到這里還有一個(gè)很大的疑惑:既然所有新進(jìn)程都是由fork產(chǎn)生的,而且由fork產(chǎn)生的子進(jìn)程和父進(jìn)程幾乎完全一樣,那豈不是意味著系統(tǒng)中所有的進(jìn)程都應(yīng)該一模一樣了嗎?而且,就我們的常識(shí)來說,當(dāng)我們執(zhí)行一個(gè)程序的時(shí)候,新產(chǎn)生的進(jìn)程的內(nèi)容應(yīng)就是程序的內(nèi)容才對(duì)。是我們理解錯(cuò)了嗎?顯然不是,要解決這些疑惑,就必須提到我們下面要介紹的exec系統(tǒng)調(diào)用。

            1.10.1 簡(jiǎn)介

            說是exec系統(tǒng)調(diào)用,實(shí)際上在Linux中,并不存在一個(gè)exec()的函數(shù)形式,exec指的是一組函數(shù),一共有6個(gè),分別是:

            #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是真正意義上的系統(tǒng)調(diào)用,其它都是在此基礎(chǔ)上經(jīng)過包裝的庫函數(shù)。

            exec函數(shù)族的作用是根據(jù)指定的文件名找到可執(zhí)行文件,并用它來取代調(diào)用進(jìn)程的內(nèi)容,換句話說,就是在調(diào)用進(jìn)程內(nèi)部執(zhí)行一個(gè)可執(zhí)行文件。這里的可執(zhí)行文件既可以是二進(jìn)制文件,也可以是任何Linux下可執(zhí)行的腳本文件。

            與一般情況不同,exec函數(shù)族的函數(shù)執(zhí)行成功后不會(huì)返回,因?yàn)檎{(diào)用進(jìn)程的實(shí)體,包括代碼段,數(shù)據(jù)段和堆棧等都已經(jīng)被新的內(nèi)容取代,只留下進(jìn)程ID等一些表面上的信息仍保持原樣,頗有些神似"三十六計(jì)"中的"金蟬脫殼"。看上去還是舊的軀殼,卻已經(jīng)注入了新的靈魂。只有調(diào)用失敗了,它們才會(huì)返回一個(gè)-1,從原程序的調(diào)用點(diǎn)接著往下執(zhí)行。

            現(xiàn)在我們應(yīng)該明白了,Linux下是如何執(zhí)行新程序的,每當(dāng)有進(jìn)程認(rèn)為自己不能為系統(tǒng)和擁護(hù)做出任何貢獻(xiàn)了,他就可以發(fā)揮最后一點(diǎn)余熱,調(diào)用任何一個(gè)exec,讓自己以新的面貌重生;或者,更普遍的情況是,如果一個(gè)進(jìn)程想執(zhí)行另一個(gè)程序,它就可以fork出一個(gè)新進(jìn)程,然后調(diào)用任何一個(gè)exec,這樣看起來就好像通過執(zhí)行應(yīng)用程序而產(chǎn)生了一個(gè)新進(jìn)程一樣。

            事實(shí)上第二種情況被應(yīng)用得如此普遍,以至于Linux專門為其作了優(yōu)化,我們已經(jīng)知道,fork會(huì)將調(diào)用進(jìn)程的所有內(nèi)容原封不動(dòng)的拷貝到新產(chǎn)生的子進(jìn)程中去,這些拷貝的動(dòng)作很消耗時(shí)間,而如果fork完之后我們馬上就調(diào)用exec,這些辛辛苦苦拷貝來的東西又會(huì)被立刻抹掉,這看起來非常不劃算,于是人們?cè)O(shè)計(jì)了一種"寫時(shí)拷貝(copy-on-write)"技術(shù),使得fork結(jié)束后并不立刻復(fù)制父進(jìn)程的內(nèi)容,而是到了真正實(shí)用的時(shí)候才復(fù)制,這樣如果下一條語句是exec,它就不會(huì)白白作無用功了,也就提高了效率。

            1.10.2 稍稍深入

            上面6條函數(shù)看起來似乎很復(fù)雜,但實(shí)際上無論是作用還是用法都非常相似,只有很微小的差別。在學(xué)習(xí)它們之前,先來了解一下我們習(xí)以為常的main函數(shù)。

            下面這個(gè)main函數(shù)的形式可能有些出乎我們的意料:

            int main(int argc, char *argv[], char *envp[])
                        

            它可能與絕大多數(shù)教科書上描述的都不一樣,但實(shí)際上,這才是main函數(shù)真正完整的形式。

            參數(shù)argc指出了運(yùn)行該程序時(shí)命令行參數(shù)的個(gè)數(shù),數(shù)組argv存放了所有的命令行參數(shù),數(shù)組envp存放了所有的環(huán)境變量。環(huán)境變量指的是一組值,從用戶登錄后就一直存在,很多應(yīng)用程序需要依靠它來確定系統(tǒng)的一些細(xì)節(jié),我們最常見的環(huán)境變量是PATH,它指出了應(yīng)到哪里去搜索應(yīng)用程序,如/bin;HOME也是比較常見的環(huán)境變量,它指出了我們?cè)谙到y(tǒng)中的個(gè)人目錄。環(huán)境變量一般以字符串"XXX=xxx"的形式存在,XXX表示變量名,xxx表示變量的值。

            值得一提的是,argv數(shù)組和envp數(shù)組存放的都是指向字符串的指針,這兩個(gè)數(shù)組都以一個(gè)NULL元素表示數(shù)組的結(jié)尾。

            我們可以通過以下這個(gè)程序來觀看傳到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
                        

            運(yùn)行時(shí),我們故意加幾個(gè)沒有任何作用的命令行參數(shù):

            $ ./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
                        

            我們看到,程序?qū)?./main"作為第1個(gè)命令行參數(shù),所以我們一共有3個(gè)命令行參數(shù)。這可能與大家平時(shí)習(xí)慣的說法有些不同,小心不要搞錯(cuò)了。

            現(xiàn)在回過頭來看一下exec函數(shù)族,先把注意力集中在execve上:

            int execve(const char *path, char *const argv[], char *const envp[]);
                        

            對(duì)比一下main函數(shù)的完整形式,看出問題了嗎?是的,這兩個(gè)函數(shù)里的argv和envp是完全一一對(duì)應(yīng)的關(guān)系。execve第1個(gè)參數(shù)path是被執(zhí)行應(yīng)用程序的完整路徑,第2個(gè)參數(shù)argv就是傳給被執(zhí)行應(yīng)用程序的命令行參數(shù),第3個(gè)參數(shù)envp是傳給被執(zhí)行應(yīng)用程序的環(huán)境變量。

            留心看一下這6個(gè)函數(shù)還可以發(fā)現(xiàn),前3個(gè)函數(shù)都是以execl開頭的,后3個(gè)都是以execv開頭的,它們的區(qū)別在于,execv開頭的函數(shù)是以"char *argv[]"這樣的形式傳遞命令行參數(shù),而execl開頭的函數(shù)采用了我們更容易習(xí)慣的方式,把參數(shù)一個(gè)一個(gè)列出來,然后以一個(gè)NULL表示結(jié)束。這里的NULL的作用和argv數(shù)組里的NULL作用是一樣的。

            在全部6個(gè)函數(shù)中,只有execle和execve使用了char *envp[]傳遞環(huán)境變量,其它的4個(gè)函數(shù)都沒有這個(gè)參數(shù),這并不意味著它們不傳遞環(huán)境變量,這4個(gè)函數(shù)將把默認(rèn)的環(huán)境變量不做任何修改地傳給被執(zhí)行的應(yīng)用程序。而execle和execve會(huì)用指定的環(huán)境變量去替代默認(rèn)的那些。

            還有2個(gè)以p結(jié)尾的函數(shù)execlp和execvp,咋看起來,它們和execl與execv的差別很小,事實(shí)也確是如此,除execlp和execvp之外的4個(gè)函數(shù)都要求,它們的第1個(gè)參數(shù)path必須是一個(gè)完整的路徑,如"/bin/ls";而execlp和execvp的第1個(gè)參數(shù)file可以簡(jiǎn)單到僅僅是一個(gè)文件名,如"ls",這兩個(gè)函數(shù)可以自動(dòng)到環(huán)境變量PATH制定的目錄里去尋找。

            1.10.3 實(shí)戰(zhàn)

            知識(shí)介紹得差不多了,接下來我們看看實(shí)際的應(yīng)用:

            /* 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");
                        }
                        

            程序里調(diào)用了2個(gè)Linux常用的系統(tǒng)命令,echo和env。echo會(huì)把后面跟的命令行參數(shù)原封不動(dòng)的打印出來,env用來列出所有環(huán)境變量。

            由于各個(gè)子進(jìn)程執(zhí)行的順序無法控制,所以有可能出現(xiàn)一個(gè)比較混亂的輸出--各子進(jìn)程打印的結(jié)果交雜在一起,而不是嚴(yán)格按照程序中列出的次序。

            編譯并運(yùn)行:

            $ 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輸出的結(jié)果跑到了execlp前面。

            大家在平時(shí)的編程中,如果用到了exec函數(shù)族,一定記得要加錯(cuò)誤判斷語句。因?yàn)榕c其他系統(tǒng)調(diào)用比起來,exec很容易受傷,被執(zhí)行文件的位置,權(quán)限等很多因素都能導(dǎo)致該調(diào)用的失敗。最常見的錯(cuò)誤是:

            1. 找不到文件或路徑,此時(shí)errno被設(shè)置為ENOENT;
            2. 數(shù)組argv和envp忘記用NULL結(jié)束,此時(shí)errno被設(shè)置為EFAULT;
            3. 沒有對(duì)要執(zhí)行文件的運(yùn)行權(quán)限,此時(shí)errno被設(shè)置為EACCES。

            原文出自http://www-128.ibm.com/developerworks/cn/linux/kernel/syscall/part3/index.html

            精品国产91久久久久久久| 久久国产三级无码一区二区| 中文字幕无码精品亚洲资源网久久| 青青热久久国产久精品| 四虎影视久久久免费观看| 久久夜色精品国产亚洲| 久久99热国产这有精品| 久久人人超碰精品CAOPOREN| 国产成人精品三上悠亚久久| 99久久精品国内| 久久婷婷午色综合夜啪| 秋霞久久国产精品电影院| 99精品国产综合久久久久五月天 | 久久精品国产99久久香蕉| 2021最新久久久视精品爱| 久久99国产精一区二区三区| 香蕉久久夜色精品国产2020| 久久香蕉国产线看观看99| 亚洲色大成网站WWW久久九九| 国产巨作麻豆欧美亚洲综合久久| 狠狠色噜噜色狠狠狠综合久久| 国产99久久九九精品无码| 久久久国产乱子伦精品作者| 久久天天躁狠狠躁夜夜2020一 | 国产精品成人久久久| 99久久久久| 99久久综合国产精品二区| 国内精品伊人久久久久av一坑| 亚洲精品无码久久不卡| 国产成人精品久久综合 | 久久无码AV一区二区三区| 99久久国产主播综合精品| 亚洲国产精品久久久天堂| 亚洲国产视频久久| 色婷婷久久综合中文久久一本| 久久精品国产99久久丝袜| 久久国产视频网| 日本久久中文字幕| 亚洲国产精品一区二区三区久久 | 久久久久国产| 日韩亚洲国产综合久久久|