殭屍進程和孤兒進程

殭屍進程和孤兒進程問題是面試筆試常考的問題,主要涉及到Linux操作系統的知識

殭屍進程:一個子進程在其父進程還沒有調用wait()或waitpid()的情況下退出。這個子進程就是殭屍進程。

孤兒進程:一個父進程退出,而它的一個或多個子進程還在運行,那麼那些子進程將成爲孤兒進程。孤兒進程將被init進程(進程號爲1)所收養,並由init進程對它們完成狀態收集工作。


殭屍進程將會導致資源浪費,而孤兒則不會。

每個進程結束的時候,系統都會掃描當前系統中所運行的所有進程,看有沒有哪個進程是剛剛結束的這個進程的子進程,如果是的話,就由Init來接管他,成爲他的父進程。


一個進程在調用exit命令結束自己的生命的時候,其實它並沒有真正的被銷燬,而是留下一個稱爲殭屍進程(Zombie)的數據結構(系統調用exit,它的作用是使進程退出,但也僅僅限於將一個正常的進程變成一個殭屍進程,並不能將其完全銷燬)。直到父進程通過wait / waitpid來取時才釋放. 但這樣就導致了問題,如果你進程不調用wait / waitpid的話, 那麼保留的那段信息就不會釋放,其進程號就會一定被佔用,但是系統所能使用的進程號是有限的,如果大量的產生殭屍進程,將因爲沒有可用的進程號而導致系統不能產生新的進程. 此即爲殭屍進程的危害,應當避免. 


殭屍進程的避免

1、父進程通過wait和waitpid等函數等待子進程結束,這會導致父進程掛起

2. 如果父進程很忙,那麼可以用signal函數爲SIGCHLD安裝handler,因爲子進程結束後, 父進程會收到該信號,可以在handler中調用wait回收

3. 如果父進程不關心子進程什麼時候結束,那麼可以用signal(SIGCHLD, SIG_IGN) 通知內核,自己對子進程的結束不感興趣,那麼子進程結束後,內核會回收, 並不再給父進程發送信號

4. 還有一些技巧,就是fork兩次,父進程fork一個子進程,然後繼續工作,子進程fork一 個孫進程後退出,那麼孫進程被init接管,孫進程結束後,init會回收。不過子進程的回收 還要自己做。

子進程結束後爲什麼要進入殭屍狀態

因爲父進程可能要取得子進程的退出狀態等信息。

殭屍狀態是每個子進程比經過的狀態嗎? 

是的。任何一個子進程(init除外)exit()之後,並非馬上就消失掉,而是留下一個稱爲殭屍進程(Zombie)的數據結構,等待父進程處理。這是每個子進程在結束時都要經過的階段。


以下是從coolshell上摘錄的原文:

Linux 的殭屍(zombie)進程

可能很少有人意識到,在一個進程調用了exit之後,該進程 並非馬上就消失掉,而是留下一個稱爲殭屍進程(Zombie)的數據結構。在Linux進程的5種狀態中,殭屍進程是非常特殊的一種,它已經放棄了幾乎所 有內存空間,沒有任何可執行代碼,也不能被調度,僅僅在進程列表中保留一個位置,記載該進程的退出狀態等信息供其他進程收集,除此之外,殭屍進程不再佔有 任何內存空間。

殭屍進程的來由,要追溯到Unix,Unix的設計者們設計這個東西並非是因爲閒來無事想裝裝酷什麼的。上面說到,殭屍進程中保存着很多對程序員和系統管理員非常重要的信息,首先,這個進程是怎麼死亡的?是正常退出呢,還是出現了錯誤,還是被其它進程強迫退出的?也就是說,這個程序的退出碼是什麼?其次,這個進程佔用的總系統CPU時間和總用戶CPU時間分別是多少?發生頁錯誤的數目和收到信號的數目。這些信息都被存儲在殭屍進程中,試想如果沒有殭屍進程,進程執行多長我們並不知道,一旦其退出,所有與之相關的信息都立刻都從系統中清除,而如果此時父進程或系統管理員需要用到,就只好乾瞪眼了。

所以,進程退出後,系統會把該進程的狀態變成Zombie,然後給上一定的時間等着父進程來收集其退出信息,因爲可能父進程正忙於別的事情來不及收集,所以,使用Zombie狀態表示進程退出了,正在等待父進程收集信息中。

Zombie進程不可以用kill命令清楚,因爲進程已退出,如果需要清除這樣的進程,那麼需要清除其父進程,或是等很長的時間後被內核清除。因爲Zombie的進程還佔着個進程ID號呢,這樣的進程如果很多的話,不利於系統的進程調度。

下面,讓我們來看看一個示例:

/* 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){ /* 如果是子進程 */ 
        exit(0);
    }else{  /* 如果是父進程 */ 
        sleep(60);  /* 休眠60秒 */ 
        wait(NULL); /* 收集殭屍進程 */
    }
}


編譯這個程序:

$ cc zombie.c -o zombie


後臺運行程序,以使我們能夠執行下一條命令

$ ./zombie &
[1] 1217


列一下系統內的進程

$ ps -ax
... ...
1137   pts/0   S   0:00   -bash
1217   pts/0   S   0:00   ./zombie
1218   pts/0   Z   0:00   [zombie]
1578   pts/0   R   0:00   ps   -ax


其中的”Z”就是殭屍進程的標誌,它表示1218號進程現在就是一個殭屍進程。

收集Zombie進程的信息,並終結這些殭屍進程,需要我們在父進程中使用waitpid調用和wait調用。這兩者的作用都是收集殭屍進程留下的信息,同時使這個進程徹底消失。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章