1. 程式人生 > >【Linux】殭屍程序的檢測,清理和避免

【Linux】殭屍程序的檢測,清理和避免

一.殭屍程序的產生

一個程序終止的方法很多,程序終止後有些資訊對於父程序和核心還是很有用的,例如程序的ID號、程序的退出狀態、程序執行的CPU時間等。因此程序在終止時,回收所有核心分配給它的記憶體、關閉它開啟的所有檔案等等,但是還會保留以上極少的資訊,以供父程序使用。父程序可以使用 wait/waitpid 等系統呼叫來為子程序收拾,做一些收尾工作。

因此,一個殭屍程序產生的過程是:父程序呼叫fork建立子程序後,子程序執行直至其終止,它立即從記憶體中移除,但程序描述符仍然保留在記憶體中(程序描述符佔有極少的記憶體空間)。子程序的狀態變成EXIT_ZOMBIE,並且向父程序傳送SIGCHLD 訊號,父程序此時應該呼叫 wait() 系統呼叫來獲取子程序的退出狀態以及其它的資訊。在 wait 呼叫之後,殭屍程序就完全從記憶體中移除。因此一個殭屍存在於其終止到父程序呼叫 wait 等函式這個時間的間隙,一般很快就消失,但如果程式設計不合理,父程序從不呼叫 wait 等系統呼叫來收集殭屍程序,那麼這些程序會一直存在記憶體中。

二.檢測系統中的殭屍程序

1.我們可以通過命令top來初步檢視系統中殭屍程序的數目:

$ top
top - 09:58:31 up 3 min,  2 users,  load average: 0.76, 0.45, 0.19
Tasks: 212 total,   1 running, 210 sleeping,   0 stopped,   1 zombie
%Cpu(s):  6.4 us,  3.1 sy,  0.6 ni, 78.6 id, 11.2 wa,  0.0 hi,  0.0 si,  0.0 st
KiB Mem:   4037576 total,  1664952 used,  2372624 free,    82416 buffers
KiB Swap:  1998844 total,        0 used,  1998844 free.   916128 cached Mem

可以看到,我們系統中有一個殭屍程序(1 zombie)。

檢視具體資訊可以用ps(殭屍程序的stat為Z):

$ ps aux | grep -w 'Z'
#或者只檢視特定的欄目:
$ ps -A -o stat,ppid,pid,cmd | grep -e '^[Zz]'

一般殭屍程序很難直接用kill殺死,因為殭屍程序是已經死掉的程序,它不能再接收任何訊號。

一個可選的解決方法是,殺死父程序(需要謹慎……),於是殭屍程序成為”孤兒程序”,它由給1號程序init收養,init 程序會週期性地去呼叫 wait 系統呼叫來清除它的殭屍孩子。因此,你會發現父程序死掉之後,殭屍程序也跟著消失,其實是 init 程序在為其收屍!

三.避免產生殭屍程序

1.一般,為了防止產生殭屍程序,在fork子程序之後我們都要wait它們;同時,當子程序退出的時候,核心都會給父程序一個SIGCHLD訊號,所以我們可以建立一個捕獲SIGCHLD訊號的訊號處理函式,在函式體中呼叫wait(或waitpid),就可以清理退出的子程序以達到防止殭屍程序的目的。
(注意,建立訊號處理函式並在其中呼叫wait並不足以防止出現殭屍程序,其原因在於:可能有多個訊號在訊號處理函式執行之前產生,而訊號處理函式只執行一次,因為Unix訊號一般是不排隊的!正確的解決辦法是呼叫waitpid而不是wait,這個辦法為:訊號處理函式中,在一個迴圈內呼叫waitpid,以獲取所有已終止子程序的狀態。我們必須指定WNOHANG選項,他告知waitpid在有尚未終止的子程序在執行時不要阻塞。)

2.在產生子程序的時候使用兩次fork(),而且緊跟著使子程序直接退出,於是孫子程序成為孤兒程序,從而init程序將負責清除這個孤兒程序。(當然,這裡需要注意,父程序仍然需要迴圈呼叫waitpid來等待子程序的結束,我們呼叫2次fork的好處是,孫子程序與父程序脫離了關係,子程序fork之後立即返回了,所有任務都交給了孫子程序去完成,這樣一來,父程序就基本不用花時間在等待子程序上了)

例子:

    /* create a new process and return if not the child */
    if ( fork() != 0 )
    {
        while (waitpid(-1, NULL, 0) > 0);   /*wait for all children*/
        return;
    }

    /*avoid zombie*/
    if ( fork() != 0)
        exit(0);
    /*
    **do something
    */