解釋一:[1,2] ●Unix編程中所謂"僵尸進(jìn)程"指什么,,什么情況下會(huì)產(chǎn)生僵尸進(jìn)程,,如何殺掉僵尸進(jìn)程: 在fork()/execve()過(guò)程中,,假設(shè)子進(jìn)程結(jié)束時(shí)父進(jìn)程仍存在,,而父進(jìn)程fork()之前既沒(méi)安裝SIGCHLD信號(hào)處理函數(shù)調(diào)用wait或waitpid()等待子進(jìn)程結(jié)束,又沒(méi)有顯式忽略該信號(hào),,則子進(jìn)程成為僵尸進(jìn)程,,無(wú)法正常結(jié)束,此時(shí)即使是root身份kill -9也不能殺死僵尸進(jìn)程(僵死進(jìn)程實(shí)際上是已死的進(jìn)程,,你當(dāng)然不能殺死一個(gè)死人) ●在一個(gè)進(jìn)程調(diào)用了exit之后,,該進(jìn)程并非馬上就消失掉,,而是留下一個(gè)稱為僵尸進(jìn)程(Zombie)的數(shù)據(jù)結(jié)構(gòu)。在Linux進(jìn)程的5種狀態(tài)中,,僵尸進(jìn)程是非常特殊的一種,,它已經(jīng)放棄了幾乎所有內(nèi)存空間,沒(méi)有任何可執(zhí)行代碼,,也不能被調(diào)度,,僅僅在進(jìn)程列表中保留一個(gè)位置,記載該進(jìn)程的退出狀態(tài)等信息供其他進(jìn)程收集(如供父進(jìn)程),,除此之外,,僵尸進(jìn)程不再占有任何內(nèi)存空間。從這點(diǎn)來(lái)看,,僵尸進(jìn)程雖然有一個(gè)很酷的名字,,但它的影響力遠(yuǎn)遠(yuǎn)抵不上那些真正的僵尸兄弟,真正的僵尸總能令人感到恐怖,,而僵尸進(jìn)程卻除了留下一些供人憑吊的信息,,對(duì)系統(tǒng)毫無(wú)作用。 ●系統(tǒng)調(diào)用exit,,它的作用是使進(jìn)程退出,,但也僅僅限于將一個(gè)正常的進(jìn)程變成一個(gè)僵尸進(jìn)程,并不能將其完全銷毀,。僵尸進(jìn)程雖然對(duì)其他進(jìn)程幾乎沒(méi)有什么影響,,不占用CPU時(shí)間,消耗的內(nèi)存也幾乎可以忽略不計(jì),,但有它在那里呆著,,還是讓人覺(jué)得心里很不舒服。而且Linux系統(tǒng)中進(jìn)程數(shù)目是有限制的,,在一些特殊的情況下,,如果存在太多的僵尸進(jìn)程,也會(huì)影響到新進(jìn)程的產(chǎn)生,。 ●如何清理僵尸進(jìn)程:用wait()和waitpid()系統(tǒng)調(diào)用可以清理僵尸進(jìn)程,或者殺死僵尸進(jìn)程的父進(jìn)程(僵尸進(jìn)程的父進(jìn)程必然存在),,僵尸進(jìn)程成為"孤兒進(jìn)程",過(guò)繼給1號(hào)進(jìn)程init,,init始終會(huì)負(fù)責(zé)清理僵尸進(jìn)程,。 下面這段比喻形容了進(jìn)程的一生,也更容易看出僵尸進(jìn)程所處的階段: 隨著一句fork,一個(gè)新進(jìn)程呱呱落地,,但它這時(shí)只是老進(jìn)程的一個(gè)克隆,。 然后隨著exec,新進(jìn)程脫胎換骨,離家獨(dú)立,,開(kāi)始了為人民服務(wù)的職業(yè)生涯,。 人有生老病死,進(jìn)程也一樣,,它可以是自然死亡,,即運(yùn)行到main函數(shù)的最后一個(gè)"}",從容地離我們而去,;也可以是自殺,,自殺有2種方式,一種是調(diào)用exit函數(shù),,一種是在main函數(shù)內(nèi)使用return,,無(wú)論哪一種方式,它都可以留下遺書,,放在返回值里保留下,;它還甚至能可被謀殺,被其它進(jìn)程通過(guò)另外一些方式結(jié)束他的生命,。 進(jìn)程死掉以后,,會(huì)留下一具僵尸,wait和waitpid充當(dāng)了殮尸工,,把僵尸推去火化,,使其最終歸于無(wú)形。 這就是進(jìn)程完整的一生,。[1,2] 處理: *顯式忽略SIGCHLD信號(hào)是指類似這樣的代碼: signal( SIGCHLD, SIG_IGN ); *安裝SIGCHLD信號(hào)句柄是指類似這樣的代碼: View Code
static void on_sigchld ( int signo ) 當(dāng)然,不建議使用signal(),,應(yīng)該使用sigaction(),。[1] 解釋二:[3] 僵尸進(jìn)程中保存著很多對(duì)程序員和系統(tǒng)管理員非常重要的信息,首先,,這個(gè)進(jìn)程是怎么死亡的,?是正常退出呢,還是出現(xiàn)了錯(cuò)誤,,還是被其它進(jìn)程強(qiáng)迫退出的,?其次,這個(gè)進(jìn)程占用的總系統(tǒng)CPU時(shí)間和總用戶 CPU時(shí)間分別是多少,?發(fā)生頁(yè)錯(cuò)誤的數(shù)目和收到信號(hào)的數(shù)目,。這些信息都被存儲(chǔ)在僵尸進(jìn)程中,試想如果沒(méi)有僵尸進(jìn)程,,進(jìn)程一退出,,所有與之相關(guān)的信息都立刻歸于無(wú)形,而此時(shí)程序員或系統(tǒng)管理員需要用到,,就只好干瞪眼了,。 那么,我們?nèi)绾问占@些信息,,并終結(jié)這些僵尸進(jìn)程呢,?就要靠waitpid調(diào)用和wait調(diào)用。這兩者的作用都是收集僵尸進(jìn)程留下的信息,,同時(shí)使這個(gè)進(jìn)程徹底消失,。 wait pid_t wait(int *statloc) 1)進(jìn)程一旦調(diào)用了wait,就立即阻塞自己,,由wait自動(dòng)分析是否當(dāng)前進(jìn)程的某個(gè)子進(jìn)程已經(jīng)退出,,如果讓它找到了這樣一個(gè)已經(jīng)變成僵尸的子進(jìn)程,wait就會(huì)收集這個(gè)子進(jìn)程的信息,,并把它徹底銷毀后返回,;如果沒(méi)有找到這樣一個(gè)子進(jìn)程,wait就會(huì)一直阻塞在這里,,直到有一個(gè)出現(xiàn)為止,。 參數(shù)statloc用來(lái)保存被收集進(jìn)程退出時(shí)的一些狀態(tài),它是一個(gè)指向int類型的指針,。但如果我們對(duì)這個(gè)子進(jìn)程是如何死掉的毫不在意,,只想把這個(gè)僵尸進(jìn)程消滅掉,(事實(shí)上絕大多數(shù)情況下,,我們都會(huì)這樣想),,我們就可以設(shè)定這個(gè)參數(shù)為NULL,就象下面這樣: pid = wait(NULL); 如果成功,,wait會(huì)返回被收集的子進(jìn)程的進(jìn)程ID,,如果調(diào)用進(jìn)程沒(méi)有子進(jìn)程,調(diào)用就會(huì)失敗,,此時(shí)wait返回-1,,同時(shí)errno被置為ECHILD。 參數(shù)statloc 如果參數(shù)statloc的值不是NULL,,wait就會(huì)把子進(jìn)程退出時(shí)的狀態(tài)取出并存入其中,,這是一個(gè)整數(shù)值(int),指出了子進(jìn)程是正常退出還是被非正常結(jié)束的(一個(gè)進(jìn)程也可以被其他進(jìn)程用信號(hào)結(jié)束),,以及正常結(jié)束時(shí)的返回值,,或被哪一個(gè)信號(hào)結(jié)束的等信息。由于這些信息被存放在一個(gè)整數(shù)的不同二進(jìn)制位中,,所以用常規(guī)的方法讀取會(huì)非常麻煩,,人們就設(shè)計(jì)了一套專門的宏(macro)來(lái)完成這項(xiàng)工作,下面我們來(lái)學(xué)習(xí)一下其中最常用的兩個(gè): ● WIFEXITED(status) 這個(gè)宏用來(lái)指出子進(jìn)程是否為正常退出的,如果是,,它會(huì)返回一個(gè)非零值,。 (請(qǐng)注意,雖然名字一樣,,這里的參數(shù)status并不同于wait唯一的參數(shù)--指向整數(shù)的指針statloc,,而是那個(gè)指針?biāo)赶虻恼麛?shù),切記不要搞混了,。) ● WEXITSTATUS(status) 當(dāng)WIFEXITED返回非零值時(shí),,我們可以用這個(gè)宏來(lái)提取子進(jìn)程的返回值,如果子進(jìn)程調(diào)用exit(5)退出,,WEXITSTATUS (status)就會(huì)返回5,;如果子進(jìn)程調(diào)用exit(7),WEXITSTATUS(status)就會(huì)返回7,。請(qǐng)注意,,如果進(jìn)程不是正常退出的,也就是說(shuō),,WIFEXITED返回0,,這個(gè)值就毫無(wú)意義。 有興趣的讀者可以自己參閱Linux man pages去了解它們的用法,。 2)進(jìn)程同步 有時(shí)候,,父進(jìn)程要求子進(jìn)程的運(yùn)算結(jié)果進(jìn)行下一步的運(yùn)算,或者子進(jìn)程的功能是為父進(jìn)程提供了下一步執(zhí)行的先決條件(如:子進(jìn)程建立文件,,而父進(jìn)程寫入數(shù)據(jù)),,此時(shí)父進(jìn)程就必須在某一個(gè)位置停下來(lái),等待子進(jìn)程運(yùn)行結(jié)束,,而如果父進(jìn)程不等待而直接執(zhí)行下去的話,,可以想見(jiàn),會(huì)出現(xiàn)極大的混亂,。這種情況稱為進(jìn)程之間的同步,,更準(zhǔn)確地說(shuō),這是進(jìn)程同步的一種特例,。進(jìn)程同步就是要協(xié)調(diào)好2個(gè)以上的進(jìn)程,,使之以安排好地次序依次執(zhí)行。 #includewaitpid pid_t waitpid(pid_t pid,int *status,int options) 從本質(zhì)上講,,系統(tǒng)調(diào)用waitpid和wait的作用是完全相同的,,但waitpid多出了兩個(gè)可由用戶控制的參數(shù)pid和options,從而為我們編程提供了另一種更靈活的方式,。下面我們就來(lái)詳細(xì)介紹一下這兩個(gè)參數(shù): ● pid 從參數(shù)的名字pid和類型pid_t中就可以看出,,這里需要的是一個(gè)進(jìn)程ID,。但當(dāng)pid取不同的值時(shí),在這里有不同的意義,。 pid>0時(shí),,只等待進(jìn)程ID等于pid的子進(jìn)程,不管其它已經(jīng)有多少子進(jìn)程運(yùn)行結(jié)束退出了,,只要指定的子進(jìn)程還沒(méi)有結(jié)束,,waitpid就會(huì)一直等下去,。 pid=-1時(shí),,等待任何一個(gè)子進(jìn)程退出,沒(méi)有任何限制,,此時(shí)waitpid和wait的作用一模一樣,。 pid=0時(shí),等待同一個(gè)進(jìn)程組中的任何子進(jìn)程,,如果子進(jìn)程已經(jīng)加入了別的進(jìn)程組,,waitpid不會(huì)對(duì)它做任何理睬。 pid<-1時(shí),,等待一個(gè)指定進(jìn)程組中的任何子進(jìn)程,,這個(gè)進(jìn)程組的ID等于pid的絕對(duì)值。 ● options options提供了一些額外的選項(xiàng)來(lái)控制waitpid,,目前在Linux中只支持WNOHANG和WUNTRACED兩個(gè)選項(xiàng),,這是兩個(gè)常數(shù),可以用"|"運(yùn)算符把它們連接起來(lái)使用,,比如: ret=waitpid(-1,NULL,WNOHANG | WUNTRACED); 如果我們不想使用它們,,也可以把options設(shè)為0,如: ret=waitpid(-1,NULL,0); 如果使用了WNOHANG參數(shù)調(diào)用waitpid,,即使沒(méi)有子進(jìn)程退出,,它也會(huì)立即返回,不會(huì)像wait那樣永遠(yuǎn)等下去,。 而WUNTRACED參數(shù),,由于涉及到一些跟蹤調(diào)試方面的知識(shí),加之極少用到,,有興趣的讀者可以自行查閱相關(guān)材料,。 可以看出,wait不就是經(jīng)過(guò)包裝的waitpid,。察看<內(nèi)核源碼目錄>/include/unistd.h文件349-352行就會(huì)發(fā)現(xiàn)以下程序段: static inline pid_t wait(int * wait_stat) { return waitpid(-1,wait_stat,0); } 返回值和錯(cuò)誤 waitpid的返回值比wait稍微復(fù)雜一些,,一共有3種情況: ● 當(dāng)正常返回的時(shí)候,waitpid返回收集到的子進(jìn)程的進(jìn)程ID,; ● 如果設(shè)置了選項(xiàng)WNOHANG,,而調(diào)用中waitpid發(fā)現(xiàn)沒(méi)有已退出的子進(jìn)程可收集,,則返回0; ● 如果調(diào)用中出錯(cuò),,則返回-1,,這時(shí)errno會(huì)被設(shè)置成相應(yīng)的值以指示錯(cuò)誤所在; 當(dāng)pid所指示的子進(jìn)程不存在,,或此進(jìn)程存在,,但不是調(diào)用進(jìn)程的子進(jìn)程,waitpid就會(huì)出錯(cuò)返回,,這時(shí)errno被設(shè)置為ECHILD,; #include
#include #include main() { pid_t pc, pr; pc=fork(); if(pc<0) /* 如果fork出錯(cuò) */ 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不會(huì)在這里等待 */ if(pr==0){ /* 如果沒(méi)有收集到子進(jìn)程 */ printf("No child exitedn"); sleep(1); } }while(pr==0); /* 沒(méi)有收集到子進(jìn)程,,就回去繼續(xù)嘗試 */ if(pr==pc) printf("successfully get child %dn", pr); else printf("some error occuredn"); } 更詳細(xì)參見(jiàn)[3],。 Linux中的進(jìn)程基本狀態(tài): 1、執(zhí)行(R)狀態(tài):CPU正在執(zhí)行,,即進(jìn)程正在占用CPU,。 2、就緒(W)狀態(tài):進(jìn)程已經(jīng)具備的執(zhí)行的一切條件,,正在等待分配CPU的處理時(shí)間片,。 3、停止(S)狀態(tài):進(jìn)程不能使用CPU,。 三個(gè)函數(shù): fork();功能:創(chuàng)建一個(gè)新的進(jìn)程,。(fork()<0[出錯(cuò)]、fork()==0[子進(jìn)程],、fork()>0[父進(jìn)程] wait();功能:真正結(jié)束進(jìn)程(收尸),。 exec();功能:執(zhí)行外部程序。[4] 參考: [1] http://blog.csdn.net/dai_weitao/archive/2007/08/01/1721184.aspx [2] http://blog.csdn.net/upcuiling/archive/2006/04/26/678498.aspx [3] http://blog.csdn.net/xjtuse_mal/archive/2007/05/31/1632185.aspx(詳細(xì)) [4] http://blog.csdn.net/stevexk/archive/2006/05/15/729215.aspx |
|