Linux 系統中殭屍程式

發表於2016-11-03

Linux 系統中殭屍程式和現實中殭屍(雖然我也沒見過)類似,雖然已經死了,但是由於沒人給它們收屍,還能四處走動。殭屍程式指的是那些雖然已經終止的程式,但仍然保留一些資訊,等待其父程式為其收屍。

殭屍程式如何產生的?

如果一個程式在其終止的時候,自己就回收所有分配給它的資源,系統就不會產生所謂的殭屍程式了。那麼我們說一個程式終止之後,還保留哪些資訊?為什麼終止之後還需要保留這些資訊呢?

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

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

在 Linux 下,我們可以使用 ps 等命令檢視系統中殭屍程式,殭屍程式的狀態標記為‘Z’:
screenshot from 2013-10-17 22 36 17

產生一個殭屍程式

根據上面的描述,我們很容易去寫一個程式來產生殭屍程式,如下程式碼:

父程式並沒有寫 wait 等系統呼叫函式,因此在子程式退出之後變成殭屍程式,父程式並沒有為其去收屍。我們使用下面命令編譯執行該程式,然後檢視系統中程式狀態:

從上面可以看出,系統中多了一個殭屍程式。但如果等父程式睡眠醒來退出之後,我們再次檢視系統程式資訊,發現剛才的殭屍程式不見了。

這是為什麼呢?父程式到死都也沒有為其子程式收屍呀,怎麼父程式退出之後,那個殭屍程式就消失了呢?難道父程式在退出時會為子程式收拾嗎?其實不然….真正的原因是:父程式死掉之後,其所有子程式過繼給 init 程式,init 程式成為該殭屍程式的新程式,init 程式會週期性地去呼叫 wait 系統呼叫來清除它的殭屍孩子。因此,你會發現上面例子中父程式死掉之後,殭屍程式也跟著消失,其實是 init 程式為其收屍的!

怎樣避免殭屍程式的產生

不能使用 kill 後接 SIGKILL 訊號這樣的命令像殺死普通程式一樣殺死殭屍程式,因為殭屍程式是已經死掉的程式,它不能再接收任何訊號。事實上,如果系統中殭屍程式並不多的話,我們也無需去消除它們,少數的殭屍程式並不會對系統的效能有什麼影響。

那麼在程式設計時,如果能避免系統中大量產生殭屍程式呢?根據上面描述的,子程式在終止時會向父程式發 SIGCHLD 訊號,Linux 預設是忽略該訊號的,我們可以顯示安裝該訊號,在訊號處理函式中呼叫 wait 等函式來為其收屍,這樣就能避免殭屍程式長期存在於系統中了。示例程式碼如下:

參考資料

相關文章