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