天天看點

孤兒程序與僵屍程序3、如何處理僵屍程序

如果你經常使用 Linux,你應該遇到這個術語“僵屍程序Zombie Processes”。 那麼什麼是僵屍程序? 它們是怎麼産生的? 它們是否對系統有害? 我要怎樣殺掉這些程序? 下面将會回答這些問題。

1、基本概念

  我們知道在unix/linux中,正常情況下,子程序是通過父程序建立的,子程序在建立新的程序。子程序的結束和父程序的運作是一個異步過程,即父程序永遠無法預測子程序 到底什麼時候結束。 當一個 程序完成它的工作終止之後,它的父程序需要調用wait()或者waitpid()系統調用取得子程序的終止狀态。

  孤兒程序:一個父程序退出,而它的一個或多個子程序還在運作,那麼那些子程序将成為孤兒程序。孤兒程序将被init程序(程序号為1)所收養,并由init程序對它們完成狀态收集工作。

  僵屍程序:一個程序使用fork建立子程序,如果子程序退出,而父程序并沒有調用wait或waitpid擷取子程序的狀态資訊,那麼子程序的程序描述符仍然儲存在系統中。這種程序稱之為僵死程序。

2、問題及危害

  unix提供了一種機制可以保證隻要父程序想知道子程序結束時的狀态資訊, 就可以得到。這種機制就是: 在每個程序退出的時候,核心釋放該程序所有的資源,包括打開的檔案,占用的記憶體等。 但是仍然為其保留一定的資訊(包括程序号the process ID,退出狀态the termination status of the process,運作時間the amount of CPU time taken by the process等)。直到父程序通過wait / waitpid來取時才釋放。 但這樣就導緻了問題,如果程序不調用wait / waitpid的話, 那麼保留的那段資訊就不會釋放,其程序号就會一直被占用,但是系統所能使用的程序号是有限的,如果大量的産生僵死程序,将因為沒有可用的程序号而導緻系統不能産生新的程序. 此即為僵屍程序的危害,應當避免。

  孤兒程序是沒有父程序的程序,孤兒程序這個重任就落到了init程序身上,init程序就好像是一個民政局,專門負責處理孤兒程序的善後工作。每當出現一個孤兒程序的時候,核心就把孤 兒程序的父程序設定為init,而init程序會循環地wait()它的已經退出的子程序。這樣,當一個孤兒程序凄涼地結束了其生命周期的時候,init程序就會代表黨和政府出面處理它的一切善後工作。是以孤兒程序并不會有什麼危害。

  任何一個子程序(init除外)在exit()之後,并非馬上就消失掉,而是留下一個稱為僵屍程序(Zombie)的資料結構,等待父程序處理。這是每個 子程序在結束時都要經過的階段。如果子程序在exit()之後,父程序沒有來得及處理,這時用ps指令就能看到子程序的狀态是“Z”。如果父程序能及時 處理,可能用ps指令就來不及看到子程序的僵屍狀态,但這并不等于子程序不經過僵屍狀态。  如果父程序在子程序結束之前退出,則子程序将由init接管。init将會以父程序的身份對僵屍狀态的子程序進行處理。

  僵屍程序危害場景:

  例如有個程序,它定期的産 生一個子程序,這個子程序需要做的事情很少,做完它該做的事情之後就退出了,是以這個子程序的生命周期很短,但是,父程序隻管生成新的子程序,至于子程序 退出之後的事情,則一概不聞不問,這樣,系統運作上一段時間之後,系統中就會存在很多的僵死程序,倘若用ps指令檢視的話,就會看到很多狀态為Z的程序。 嚴格地來說,僵死程序并不是問題的根源,罪魁禍首是産生出大量僵死程序的那個父程序。是以,當我們尋求如何消滅系統中大量的僵死程序時,答案就是把産生大 量僵死程序的那個元兇槍斃掉(也就是通過kill發送SIGTERM或者SIGKILL信号啦)。槍斃了元兇程序之後,它産生的僵死程序就變成了孤兒進 程,這些孤兒程序會被init程序接管,init程序會wait()這些孤兒程序,釋放它們占用的系統程序表中的資源,這樣,這些已經僵死的孤兒程序 就能瞑目而去了。

3、如何處理僵屍程序

僵屍程序的産生是因為父程序沒有 wait() 子程序。是以如果我們自己寫程式的話一定要在父程序中通過 wait() 來避免僵屍程序的産生。

當系統中出現了僵屍程序時,我們是無法通過 kill 指令把它清除掉的。但是我們可以殺死它的父程序,讓它變成孤兒程序,并進一步被系統中管理孤兒程序的程序收養并清理。

那麼如何找出僵屍程序呢?

打開終端并輸入下面指令:

ps aux | grep Z
           

會列出程序表中所有僵屍程序的詳細内容。

ps -ef |grep <程序名或者PID>
在顯示的輸出中,第三列就是該程序的父程序PID,然後可以再使用ps指令來檢視父程序的名稱
ps -ef |grep <父程序PID>
           
孤兒程式與僵屍程式3、如何處理僵屍程式

如何殺掉僵屍程序?

正常情況下我們可以用 

SIGKILL

 信号來殺死程序,但是僵屍程序已經死了, 你不能殺死已經死掉的東西。 是以你需要輸入的指令應該是

kill -s SIGCHLD pid
           

将這裡的 pid 替換成父程序的程序 id,這樣父程序就會删除所有以及完成并死掉的子程序了。

你可以把它想象成:

"你在道路中間發現一具屍體,于是你聯系了死者的家屬,随後他們就會将屍體帶離道路了。"

不過許多程式寫的不是那麼好,無法删掉這些子僵屍(否則你一開始也見不到這些僵屍了)。 是以確定删除子僵屍的唯一方法就是殺掉它們的父程序。

<?php
 
/**
 * PHP Linux Cli 模式下利用 pcntl_fork實作多程序處理
 * 
 */
 
// 程序數
$processes = 5;
 
// 所有任務就是是為了輸出0到9999中的所有數字
// 這些數字将被分成5個塊,代表5個程序,
// 當輸出的時候我們就可以看到所有程序的執行順序
$tasks = range ( 0, 9999 );
 
$blocks = array ();
 
// 将任務按程序分塊
foreach ( $tasks as $i ) {
	
	$blocks [($i % $processes)] [] = $i;
}
 
foreach ( $blocks as $blockNum => $block ) {
	
	// 通過pcntl得到一個子程序的PID
	$pid = pcntl_fork ();
	
	if ($pid == - 1) {
		// 錯誤處理:建立子程序失敗時傳回-1.
		die ( 'could not fork' );
	} else if ($pid) {
		// 父程序邏輯
		// 等待子程序中斷,防止子程序成為僵屍程序。
		// WNOHANG為非阻塞程序,具體請查閱pcntl_wait PHP官方文檔
		pcntl_wait ( $status, WNOHANG );
	} else {
		// 子程序邏輯
		foreach ( $block as $i ) {
			
			echo "I'm block {$blockNum},I'm  printing:{$i}\n";
			sleep ( 1 );
		}
		
		// 為避免僵屍程序,當子程序結束後,手動殺死程序
		if (function_exists ( "posix_kill" )) {
			posix_kill ( getmypid (), SIGTERM );
		} else {
			system ( 'kill -9' . getmypid () );
		}
		exit ();
	}
}
           

繼續閱讀