一区二区三区日韩精品-日韩经典一区二区三区-五月激情综合丁香婷婷-欧美精品中文字幕专区

分享

Linux下的進(jìn)程的

 小換換 2009-08-04
本文介紹了Linux下的進(jìn)程的一些概念,并著重講解了與Linux進(jìn)程管理相關(guān)的重要系統(tǒng)調(diào)用wait,waitpid和exec函數(shù)族,輔助一些例程說明了它們的特點和使用方法。

1.7 背景

在前面的文章中,我們已經(jīng)了解了父進(jìn)程和子進(jìn)程的概念,并已經(jīng)掌握了系統(tǒng)調(diào)用exit的用法,但可能很少有人意識到,在一個進(jìn)程調(diào)用了exit之后,該進(jìn)程并非馬上就消失掉,而是留下一個稱為僵尸進(jìn)程(Zombie)的數(shù)據(jù)結(jié)構(gòu)。在Linux進(jìn)程的5種狀態(tài)中,僵尸進(jìn)程是非常特殊的一種,它已經(jīng)放棄了幾乎所有內(nèi)存空間,沒有任何可執(zhí)行代碼,也不能被調(diào)度,僅僅在進(jìn)程列表中保留一個位置,記載該進(jìn)程的退出狀態(tài)等信息供其他進(jìn)程收集,除此之外,僵尸進(jìn)程不再占有任何內(nèi)存空間。從這點來看,僵尸進(jìn)程雖然有一個很酷的名字,但它的影響力遠(yuǎn)遠(yuǎn)抵不上那些真正的僵尸兄弟,真正的僵尸總能令人感到恐怖,而僵尸進(jìn)程卻除了留下一些供人憑吊的信息,對系統(tǒng)毫無作用。

也許讀者們還對這個新概念比較好奇,那就讓我們來看一眼Linux里的僵尸進(jìn)程究竟長什么樣子。

當(dāng)一個進(jìn)程已退出,但其父進(jìn)程還沒有調(diào)用系統(tǒng)調(diào)用wait(稍后介紹)對其進(jìn)行收集之前的這段時間里,它會一直保持僵尸狀態(tài),利用這個特點,我們來寫一個簡單的小程序:

/* zombie.c */
            #include <sys/types.h>
            #include <unistd.h>
            main()
            {
            pid_t pid;
            pid=fork();
            if(pid<0)	/* 如果出錯 */
            printf("error occurred!\n");
            else if(pid==0) /* 如果是子進(jìn)程 */
            exit(0);
            else		/* 如果是父進(jìn)程 */
            sleep(60);	/* 休眠60秒,這段時間里,父進(jìn)程什么也干不了 */
            wait(NULL);	/* 收集僵尸進(jìn)程 */
            }
            

sleep的作用是讓進(jìn)程休眠指定的秒數(shù),在這60秒內(nèi),子進(jìn)程已經(jīng)退出,而父進(jìn)程正忙著睡覺,不可能對它進(jìn)行收集,這樣,我們就能保持子進(jìn)程60秒的僵尸狀態(tài)。

編譯這個程序:

$ cc zombie.c -o zombie
            

后臺運行程序,以使我們能夠執(zhí)行下一條命令

$ ./zombie &
            [1] 1577
            

列一下系統(tǒng)內(nèi)的進(jìn)程

$ ps -ax
            ...  ...
            1177 pts/0    S      0:00 -bash
            1577 pts/0    S      0:00 ./zombie
            1578 pts/0    Z      0:00 [zombie <defunct>]
            1579 pts/0    R      0:00 ps -ax
            

看到中間的"Z"了嗎?那就是僵尸進(jìn)程的標(biāo)志,它表示1578號進(jìn)程現(xiàn)在就是一個僵尸進(jìn)程。

我們已經(jīng)學(xué)習(xí)了系統(tǒng)調(diào)用exit,它的作用是使進(jìn)程退出,但也僅僅限于將一個正常的進(jìn)程變成一個僵尸進(jìn)程,并不能將其完全銷毀。僵尸進(jìn)程雖然對其他進(jìn)程幾乎沒有什么影響,不占用CPU時間,消耗的內(nèi)存也幾乎可以忽略不計,但有它在那里呆著,還是讓人覺得心里很不舒服。而且Linux系統(tǒng)中進(jìn)程數(shù)目是有限制的,在一些特殊的情況下,如果存在太多的僵尸進(jìn)程,也會影響到新進(jìn)程的產(chǎn)生。那么,我們該如何來消滅這些僵尸進(jìn)程呢?

先來了解一下僵尸進(jìn)程的來由,我們知道,Linux和UNIX總有著剪不斷理還亂的親緣關(guān)系,僵尸進(jìn)程的概念也是從UNIX上繼承來的,而UNIX的先驅(qū)們設(shè)計這個東西并非是因為閑來無聊想煩煩其他的程序員。僵尸進(jìn)程中保存著很多對程序員和系統(tǒng)管理員非常重要的信息,首先,這個進(jìn)程是怎么死亡的?是正常退出呢,還是出現(xiàn)了錯誤,還是被其它進(jìn)程強(qiáng)迫退出的?其次,這個進(jìn)程占用的總系統(tǒng)CPU時間和總用戶CPU時間分別是多少?發(fā)生頁錯誤的數(shù)目和收到信號的數(shù)目。這些信息都被存儲在僵尸進(jìn)程中,試想如果沒有僵尸進(jìn)程,進(jìn)程一退出,所有與之相關(guān)的信息都立刻歸于無形,而此時程序員或系統(tǒng)管理員需要用到,就只好干瞪眼了。

那么,我們?nèi)绾问占@些信息,并終結(jié)這些僵尸進(jìn)程呢?就要靠我們下面要講到的waitpid調(diào)用和wait調(diào)用。這兩者的作用都是收集僵尸進(jìn)程留下的信息,同時使這個進(jìn)程徹底消失。下面就對這兩個調(diào)用分別作詳細(xì)介紹。





回頁首


1.8 wait

1.8.1 簡介

wait的函數(shù)原型是:

		#include <sys/types.h> /* 提供類型pid_t的定義 */
            #include <sys/wait.h>
            pid_t wait(int *status)
            

進(jìn)程一旦調(diào)用了wait,就立即阻塞自己,由wait自動分析是否當(dāng)前進(jìn)程的某個子進(jìn)程已經(jīng)退出,如果讓它找到了這樣一個已經(jīng)變成僵尸的子進(jìn)程,wait就會收集這個子進(jìn)程的信息,并把它徹底銷毀后返回;如果沒有找到這樣一個子進(jìn)程,wait就會一直阻塞在這里,直到有一個出現(xiàn)為止。

參數(shù)status用來保存被收集進(jìn)程退出時的一些狀態(tài),它是一個指向int類型的指針。但如果我們對這個子進(jìn)程是如何死掉的毫不在意,只想把這個僵尸進(jìn)程消滅掉,(事實上絕大多數(shù)情況下,我們都會這樣想),我們就可以設(shè)定這個參數(shù)為NULL,就象下面這樣:

		pid = wait(NULL);
            

如果成功,wait會返回被收集的子進(jìn)程的進(jìn)程ID,如果調(diào)用進(jìn)程沒有子進(jìn)程,調(diào)用就會失敗,此時wait返回-1,同時errno被置為ECHILD。

1.8.2 實戰(zhàn)

下面就讓我們用一個例子來實戰(zhàn)應(yīng)用一下wait調(diào)用,程序中用到了系統(tǒng)調(diào)用fork,如果你對此不大熟悉或已經(jīng)忘記了,請參考上一篇文章《進(jìn)程管理相關(guān)的系統(tǒng)調(diào)用(一)》。

/* wait1.c */
            #include <sys/types.h>
            #include <sys/wait.h>
            #include <unistd.h>
            #include <stdlib.h>
            main()
            {
            pid_t pc,pr;
            pc=fork();
            if(pc<0) 		/* 如果出錯 */
            printf("error ocurred!\n");
            else if(pc==0){		/* 如果是子進(jìn)程 */
            printf("This is child process with pid of %d\n",getpid());
            sleep(10);	/* 睡眠10秒鐘 */
            }
            else{			/* 如果是父進(jìn)程 */
            pr=wait(NULL);	/* 在這里等待 */
            printf("I catched a child process with pid of %d\n"),pr);
            }
            exit(0);
            }
            

編譯并運行:

$ cc wait1.c -o wait1
            $ ./wait1
            This is child process with pid of 1508
            I catched a child process with pid of 1508
            

可以明顯注意到,在第2行結(jié)果打印出來前有10秒鐘的等待時間,這就是我們設(shè)定的讓子進(jìn)程睡眠的時間,只有子進(jìn)程從睡眠中蘇醒過來,它才能正常退出,也就才能被父進(jìn)程捕捉到。其實這里我們不管設(shè)定子進(jìn)程睡眠的時間有多長,父進(jìn)程都會一直等待下去,讀者如果有興趣的話,可以試著自己修改一下這個數(shù)值,看看會出現(xiàn)怎樣的結(jié)果。

1.8.3 參數(shù)status

如果參數(shù)status的值不是NULL,wait就會把子進(jìn)程退出時的狀態(tài)取出并存入其中,這是一個整數(shù)值(int),指出了子進(jìn)程是正常退出還是被非正常結(jié)束的(一個進(jìn)程也可以被其他進(jìn)程用信號結(jié)束,我們將在以后的文章中介紹),以及正常結(jié)束時的返回值,或被哪一個信號結(jié)束的等信息。由于這些信息被存放在一個整數(shù)的不同二進(jìn)制位中,所以用常規(guī)的方法讀取會非常麻煩,人們就設(shè)計了一套專門的宏(macro)來完成這項工作,下面我們來學(xué)習(xí)一下其中最常用的兩個:

1,WIFEXITED(status) 這個宏用來指出子進(jìn)程是否為正常退出的,如果是,它會返回一個非零值。

(請注意,雖然名字一樣,這里的參數(shù)status并不同于wait唯一的參數(shù)--指向整數(shù)的指針status,而是那個指針?biāo)赶虻恼麛?shù),切記不要搞混了。)

2,WEXITSTATUS(status) 當(dāng)WIFEXITED返回非零值時,我們可以用這個宏來提取子進(jìn)程的返回值,如果子進(jìn)程調(diào)用exit(5)退出,WEXITSTATUS(status)就會返回5;如果子進(jìn)程調(diào)用exit(7),WEXITSTATUS(status)就會返回7。請注意,如果進(jìn)程不是正常退出的,也就是說,WIFEXITED返回0,這個值就毫無意義。

下面通過例子來實戰(zhàn)一下我們剛剛學(xué)到的內(nèi)容:

/* wait2.c */
            #include <sys/types.h>
            #include <sys/wait.h>
            #include <unistd.h>
            main()
            {
            int status;
            pid_t pc,pr;
            pc=fork();
            if(pc<0)	/* 如果出錯 */
            printf("error ocurred!\n");
            else if(pc==0){	/* 子進(jìn)程 */
            printf("This is child process with pid of %d.\n",getpid());
            exit(3);	/* 子進(jìn)程返回3 */
            }
            else{		/* 父進(jìn)程 */
            pr=wait(&status);
            if(WIFEXITED(status)){	/* 如果WIFEXITED返回非零值 */
            printf("the child process %d exit normally.\n",pr);
            printf("the return code is %d.\n",WEXITSTATUS(status));
            }else			/* 如果WIFEXITED返回零 */
            printf("the child process %d exit abnormally.\n",pr);
            }
            }
            

編譯并運行:

$ cc wait2.c -o wait2
            $ ./wait2
            This is child process with pid of 1538.
            the child process 1538 exit normally.
            the return code is 3.
            

父進(jìn)程準(zhǔn)確捕捉到了子進(jìn)程的返回值3,并把它打印了出來。

當(dāng)然,處理進(jìn)程退出狀態(tài)的宏并不止這兩個,但它們當(dāng)中的絕大部分在平時的編程中很少用到,就也不在這里浪費篇幅介紹了,有興趣的讀者可以自己參閱Linux man pages去了解它們的用法。

1.8.4 進(jìn)程同步

有時候,父進(jìn)程要求子進(jìn)程的運算結(jié)果進(jìn)行下一步的運算,或者子進(jìn)程的功能是為父進(jìn)程提供了下一步執(zhí)行的先決條件(如:子進(jìn)程建立文件,而父進(jìn)程寫入數(shù)據(jù)),此時父進(jìn)程就必須在某一個位置停下來,等待子進(jìn)程運行結(jié)束,而如果父進(jìn)程不等待而直接執(zhí)行下去的話,可以想見,會出現(xiàn)極大的混亂。這種情況稱為進(jìn)程之間的同步,更準(zhǔn)確地說,這是進(jìn)程同步的一種特例。進(jìn)程同步就是要協(xié)調(diào)好2個以上的進(jìn)程,使之以安排好地次序依次執(zhí)行。解決進(jìn)程同步問題有更通用的方法,我們將在以后介紹,但對于我們假設(shè)的這種情況,則完全可以用wait系統(tǒng)調(diào)用簡單的予以解決。請看下面這段程序:

#include <sys/types.h>
            #include <sys/wait.h>
            main()
            {
            pid_t pc, pr;
            int status;
            pc=fork();
            if(pc<0)
            printf("Error occured on forking.\n");
            else if(pc==0){
            /* 子進(jìn)程的工作 */
            exit(0);
            }else{
            /* 父進(jìn)程的工作 */
            pr=wait(&status);
            /* 利用子進(jìn)程的結(jié)果 */
            }
            }
            

這段程序只是個例子,不能真正拿來執(zhí)行,但它卻說明了一些問題,首先,當(dāng)fork調(diào)用成功后,父子進(jìn)程各做各的事情,但當(dāng)父進(jìn)程的工作告一段落,需要用到子進(jìn)程的結(jié)果時,它就停下來調(diào)用wait,一直等到子進(jìn)程運行結(jié)束,然后利用子進(jìn)程的結(jié)果繼續(xù)執(zhí)行,這樣就圓滿地解決了我們提出的進(jìn)程同步問題。





回頁首


1.9 waitpid

1.9.1 簡介

waitpid系統(tǒng)調(diào)用在Linux函數(shù)庫中的原型是:

		#include <sys/types.h> /* 提供類型pid_t的定義 */
            #include <sys/wait.h>
            pid_t waitpid(pid_t pid,int *status,int options)
            

從本質(zhì)上講,系統(tǒng)調(diào)用waitpid和wait的作用是完全相同的,但waitpid多出了兩個可由用戶控制的參數(shù)pid和options,從而為我們編程提供了另一種更靈活的方式。下面我們就來詳細(xì)介紹一下這兩個參數(shù):

pid

從參數(shù)的名字pid和類型pid_t中就可以看出,這里需要的是一個進(jìn)程ID。但當(dāng)pid取不同的值時,在這里有不同的意義。

  1. pid>0時,只等待進(jìn)程ID等于pid的子進(jìn)程,不管其它已經(jīng)有多少子進(jìn)程運行結(jié)束退出了,只要指定的子進(jìn)程還沒有結(jié)束,waitpid就會一直等下去。
  2. pid=-1時,等待任何一個子進(jìn)程退出,沒有任何限制,此時waitpid和wait的作用一模一樣。
  3. pid=0時,等待同一個進(jìn)程組中的任何子進(jìn)程,如果子進(jìn)程已經(jīng)加入了別的進(jìn)程組,waitpid不會對它做任何理睬。
  4. pid<-1時,等待一個指定進(jìn)程組中的任何子進(jìn)程,這個進(jìn)程組的ID等于pid的絕對值。

options

options提供了一些額外的選項來控制waitpid,目前在Linux中只支持WNOHANG和WUNTRACED兩個選項,這是兩個常數(shù),可以用"|"運算符把它們連接起來使用,比如:

ret=waitpid(-1,NULL,WNOHANG | WUNTRACED);
            

如果我們不想使用它們,也可以把options設(shè)為0,如:

ret=waitpid(-1,NULL,0);
            

如果使用了WNOHANG參數(shù)調(diào)用waitpid,即使沒有子進(jìn)程退出,它也會立即返回,不會像wait那樣永遠(yuǎn)等下去。

而WUNTRACED參數(shù),由于涉及到一些跟蹤調(diào)試方面的知識,加之極少用到,這里就不多費筆墨了,有興趣的讀者可以自行查閱相關(guān)材料。

看到這里,聰明的讀者可能已經(jīng)看出端倪了--wait不就是經(jīng)過包裝的waitpid嗎?沒錯,察看<內(nèi)核源碼目錄>/include/unistd.h文件349-352行就會發(fā)現(xiàn)以下程序段:

static inline pid_t wait(int * wait_stat)
            {
            return waitpid(-1,wait_stat,0);
            }
            

1.9.2 返回值和錯誤

waitpid的返回值比wait稍微復(fù)雜一些,一共有3種情況:

  1. 當(dāng)正常返回的時候,waitpid返回收集到的子進(jìn)程的進(jìn)程ID;
  2. 如果設(shè)置了選項WNOHANG,而調(diào)用中waitpid發(fā)現(xiàn)沒有已退出的子進(jìn)程可收集,則返回0;
  3. 如果調(diào)用中出錯,則返回-1,這時errno會被設(shè)置成相應(yīng)的值以指示錯誤所在;

當(dāng)pid所指示的子進(jìn)程不存在,或此進(jìn)程存在,但不是調(diào)用進(jìn)程的子進(jìn)程,waitpid就會出錯返回,這時errno被設(shè)置為ECHILD;

/* waitpid.c */
            #include <sys/types.h>
            #include <sys/wait.h>
            #include <unistd.h>
            main()
            {
            pid_t pc, pr;
            pc=fork();
            if(pc<0)		/* 如果fork出錯 */
            printf("Error occured on forking.\n");
            else if(pc==0){		/* 如果是子進(jìn)程 */
            sleep(10);	/* 睡眠10秒 */
            exit(0);
            }
            /* 如果是父進(jìn)程 */
            do{
            pr=waitpid(pc, NULL, WNOHANG);	/* 使用了WNOHANG參數(shù),waitpid不會在這里等待 */
            if(pr==0){			/* 如果沒有收集到子進(jìn)程 */
            printf("No child exited\n");
            sleep(1);
            }
            }while(pr==0);				/* 沒有收集到子進(jìn)程,就回去繼續(xù)嘗試 */
            if(pr==pc)
            printf("successfully get child %d\n", pr);
            else
            printf("some error occured\n");
            }
            

編譯并運行:

$ cc waitpid.c -o waitpid
            $ ./waitpid
            No child exited
            No child exited
            No child exited
            No child exited
            No child exited
            No child exited
            No child exited
            No child exited
            No child exited
            No child exited
            successfully get child 1526
            

父進(jìn)程經(jīng)過10次失敗的嘗試之后,終于收集到了退出的子進(jìn)程。

因為這只是一個例子程序,不便寫得太復(fù)雜,所以我們就讓父進(jìn)程和子進(jìn)程分別睡眠了10秒鐘和1秒鐘,代表它們分別作了10秒鐘和1秒鐘的工作。父子進(jìn)程都有工作要做,父進(jìn)程利用工作的簡短間歇察看子進(jìn)程的是否退出,如退出就收集它。





回頁首


1.10 exec

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

1.10.1 簡介

說是exec系統(tǒng)調(diào)用,實際上在Linux中,并不存在一個exec()的函數(shù)形式,exec指的是一組函數(shù),一共有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是真正意義上的系統(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í)行一個可執(zhí)行文件。這里的可執(zhí)行文件既可以是二進(jìn)制文件,也可以是任何Linux下可執(zhí)行的腳本文件。

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

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

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

1.10.2 稍稍深入

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

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

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

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

參數(shù)argc指出了運行該程序時命令行參數(shù)的個數(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)境變量,它指出了我們在系統(tǒng)中的個人目錄。環(huán)境變量一般以字符串"XXX=xxx"的形式存在,XXX表示變量名,xxx表示變量的值。

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

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

運行時,我們故意加幾個沒有任何作用的命令行參數(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個命令行參數(shù),所以我們一共有3個命令行參數(shù)。這可能與大家平時習(xí)慣的說法有些不同,小心不要搞錯了。

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

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

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

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

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

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

1.10.3 實戰(zhàn)

知識介紹得差不多了,接下來我們看看實際的應(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個Linux常用的系統(tǒng)命令,echo和env。echo會把后面跟的命令行參數(shù)原封不動的打印出來,env用來列出所有環(huán)境變量。

由于各個子進(jìn)程執(zhí)行的順序無法控制,所以有可能出現(xiàn)一個比較混亂的輸出--各子進(jìn)程打印的結(jié)果交雜在一起,而不是嚴(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前面。

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

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




回頁首


1.11 進(jìn)程的一生

下面就讓我用一些形象的比喻,來對進(jìn)程短暫的一生作一個小小的總結(jié):

隨著一句fork,一個新進(jìn)程呱呱落地,但它這時只是老進(jìn)程的一個克隆。

然后隨著exec,新進(jìn)程脫胎換骨,離家獨立,開始了為人民服務(wù)的職業(yè)生涯。

人有生老病死,進(jìn)程也一樣,它可以是自然死亡,即運行到main函數(shù)的最后一個"}",從容地離我們而去;也可以是自殺,自殺有2種方式,一種是調(diào)用exit函數(shù),一種是在main函數(shù)內(nèi)使用return,無論哪一種方式,它都可以留下遺書,放在返回值里保留下來;它還甚至能可被謀殺,被其它進(jìn)程通過另外一些方式結(jié)束他的生命。

進(jìn)程死掉以后,會留下一具僵尸,wait和waitpid充當(dāng)了殮尸工,把僵尸推去火化,使其最終歸于無形。

這就是進(jìn)程完整的一生。





回頁首


1.12 小結(jié)

本文重點介紹了系統(tǒng)調(diào)用wait、waitpid和exec函數(shù)族,對與進(jìn)程管理相關(guān)的系統(tǒng)調(diào)用的介紹就在這里告一段落,在下一篇文章,也是與進(jìn)程管理相關(guān)的系統(tǒng)調(diào)用的最后一篇文章中,我們會通過兩個很酷的實際例子,來重溫一下最近學(xué)過的知識。



參考資料

  • Linux man pages

  • Advanced Programming in the UNIX Environment by W. Richard Stevens, 1993

  • Linux核心源代碼分析 彭曉明,王強(qiáng),2000

    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊一鍵舉報。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多

    亚洲最新中文字幕在线视频| 中文人妻精品一区二区三区四区| 国产级别精品一区二区视频| 中文字幕亚洲人妻在线视频| 亚洲一区二区三区av高清| 欧美日韩精品视频在线| 国产一区二区三区香蕉av| 国产黑人一区二区三区| 欧美中文字幕一区在线| 色婷婷激情五月天丁香| 中文字幕乱码一区二区三区四区| 日韩精品视频一二三区| 精品一区二区三区中文字幕| 国产精品伦一区二区三区四季| 日本在线高清精品人妻| 午夜成年人黄片免费观看| 最新午夜福利视频偷拍| 欧洲一区二区三区自拍天堂| 欧美精品日韩精品一区| 日本婷婷色大香蕉视频在线观看| 亚洲精品偷拍视频免费观看| 欧美日韩国产综合特黄| 都市激情小说在线一区二区三区| 中文久久乱码一区二区| 99久热只有精品视频最新| 美女被后入视频在线观看| 97人妻精品一区二区三区男同| 久久这里只精品免费福利| 国产日韩久久精品一区| 国产日韩精品激情在线观看| 免费在线播放不卡视频| 国产一区二区三区草莓av| 好吊日成人免费视频公开| 国产户外勾引精品露出一区 | 日韩黄色大片免费在线| 国产欧洲亚洲日产一区二区| 午夜激情视频一区二区| 久久精品国产亚洲av久按摩| 日韩国产亚洲一区二区三区| 亚洲中文字幕有码在线观看| 国产成人精品99在线观看|