殭屍程序詳解

2021-06-05 07:06:04 字數 3140 閱讀 4941

//xk> 鋪墊1:fork()和exec

unix中派生乙個新的程序的唯一方法是fork()函式(有些系統可能提供它的各種變體)。父程序中開啟的所有描述符在fork()之後由子程序分享。

存放在硬碟上的程式檔案能夠被unix執行的唯一途徑是:由乙個現有程序呼叫exec函式中的某乙個(exec函式有6個,作用相同,呼叫引數不同)。exec不會建立乙個新的程序,而是用程式檔案替換掉當前程序(即呼叫exec的程序,又叫呼叫程序)映像,而且該新程式通常從main()開始執行,程序id不變,並且預設情況下呼叫程序開啟的描述符跨exec繼續保持開啟(可以使用fcntl設定fd_cloexec描述符標誌禁止)。

因為exec會替換掉原來的程序映像,所以常常是先fork(),然後在子程序中呼叫exec。

//xk> 鋪墊2:main()和exit()

c程式總是從main()開始執行,當核心使用乙個exec函式執行c程式時,會先呼叫乙個啟動例程,啟動例程從核心取得命令列引數和環境變數值。在通過c源程式建立可執行檔案時,聯結器會將系統啟動例程指定為程式的起始位址。

啟動例程通常用組合語言編寫,表示成c語言的形式則為:

exit(main(argc, argv));

使得從main()返回後立即呼叫exit()。exit()主要做兩個事情:1. 執行標準i/o庫的清理關閉操作。為所有開啟流呼叫fclose(),這會造成所有緩衝的輸出資料都被flush到檔案上。2. 返回exit status. 通常就是main()的返回值。

//xk> 鋪墊3:程序終止

有8種方法終止程序。

5種正常終止:

1. 從main()返回。

2. 呼叫exit(). 執行一些清理處理(呼叫執行各atexit()設定的終止處理程式,關閉所有標準i/o流等),然後進入核心,應該還是呼叫了_exit()系統呼叫。

3. 呼叫_exit()或_exit(). 不執行清理操作立即進入核心。_exit()是由posix.1說明的,_exit()和exit()是由iso c說明的。而核心對_exit()系統呼叫的處理是釋放程序所擁有的資源並向父程序傳送sigchld訊號(預設處理是忽略)。

4. 最後乙個執行緒從其啟動例程返回。

5. 最後乙個執行緒呼叫pthread_exit().

3種異常終止:

1. 呼叫abort(). 此函式將sigabrt訊號傳送給呼叫程序。

2. 接到乙個訊號並終止。對多數訊號,如果程序沒有捕捉它,通常程序將會立即終止,並將程序在記憶體中的映像生成為核心轉儲檔案core放在當前目錄下。

3. 最後乙個執行緒對取消請求做出響應。執行緒可以被同一程序中的其它執行緒cancel掉。

//xk> 鋪墊4:unix訊號一般是不排隊的

當引發訊號的事件發生時,核心會產生乙個訊號,然後遞送給相應的程序,這通常是由核心在程序表中設定乙個某種形式的標誌,總之核心做這些動作是需要時間的,在訊號產生和遞送之間的時間間隔內,稱訊號是未決的(pending)。並且,程序還可以設定訊號遞送阻塞,如果為該程序產生了乙個選擇為阻塞的訊號,而且對該訊號的處理是系統預設動作或捕捉該訊號,則核心將此訊號保持為未決狀態,直到程序對該訊號解除了阻塞或者對此訊號的動作改為忽略。

如果訊號成功遞送到程序之前,這種訊號發生了多次,會怎麼樣呢?大多數unix並不對訊號排隊,即系統只會遞送該訊號一次。支援posix.1實時擴充套件的系統才會遞送該訊號多次。

//xk> 好,下面開始

//xk>----------------------------------------------------------------

在很多場景下需要用到fork()來派生乙個子程序,這時需要注意殭屍程序的問題。

//xk> 殭屍程序的產生

我們一般認為,程序終止後系統會自動**分配給該程序的所有資源。然而,當乙個子程序終止時,它其實是處於「僵而不死」的狀態,因為父程序可能需要檢測子程序終止的退出碼。所以子程序的執行流程雖然結束,但它仍然存在於系統中,程序表中代表子程序的表項還沒有釋放,直到父程序也正常終止或父程序呼叫wait()/waitpid()檢查了已終止子程序的退出狀態。如果父程序異常終止,則子程序自動把pid為1的程序(即init程序)作為自己的父程序,殭屍程序會一直保留在程序表中直到被init程序發現並釋放。

//xk> 殭屍程序的處理

在父程序中可以通過呼叫wait()來讓父程序等待子程序的結束並檢查子程序的exit status。如果子程序尚未結束,則父程序會被阻塞,等待子程序的結束。如果子程序已經結束,則會超度已經僵死的子程序,釋放資源。

如果不希望父程序被阻塞咋辦呢?子程序結束時會向父程序傳送sigchld訊號,可以把wait()呼叫放到sigchid訊號的訊號處理函式中。

但是小心!建立乙個訊號處理函式並在其中呼叫wait()並不足以防止產生殭屍程序。考慮下面的情景:

因為unix程序比較輕量級,在unix中編寫併發伺服器程式最簡單的辦法就是fork乙個子程序來服務每個客戶。現在假設在乙個客戶程序中開啟n個socket描述符都與這樣的併發伺服器建立tcp連線(模擬伺服器負載很大時多個客戶幾乎同時建立連線或斷開連線),那麼伺服器就會fork出n個子程序來響應這n個連線請求。當客戶程序終止時,n個socket描述符幾乎同時關閉,每個socket描述符關閉時都會發出fin報文通知伺服器客戶端不會再向socket寫資料了。假設伺服器子程序的處理是接收客戶端發來的資料,直到收到fin報文則程式執行結束,那麼n個伺服器子程序幾乎同時結束,幾乎同時向併發伺服器父程序傳送sigchld訊號。悲劇的是,unix訊號一般是不排隊的,所以這麼做很容易造成一些伺服器子程序終止的sigchld訊號丟失,這些子程序不會被父程序wait()到,也就變成了殭屍程序。

正確的解決辦法是呼叫waitpid(). 相比wait()這個函式有兩點強大的地方:1. 可以指定子程序id號。2. 可以設定wnohang選項讓父程序在指定的子程序尚未終止時不要阻塞。

針對上面設定的情景,併發伺服器程式可以隔一段時間用waitpid()遍歷一次所有的子程序,清掉殭屍子程序。注意必須設定wnohang選項,併發伺服器被阻塞是不可忍受的。

Linux殭屍程序詳解

1.殭屍程序概念 殭屍程序 zombie process 就是已經結束了的程序,但是沒有從程序表中刪除。太多了會導致程序表裡面條目滿了,進而導致系統崩潰,倒是不占用其他系統資源。在linux 程序的狀態中,殭屍程序是非常特殊的一種,它已經放棄了幾乎所有記憶體空間,沒有任何可執行 也不能被排程,僅僅在...

Linux中殭屍程序和孤兒程序詳解

1 殭屍程序 乙個子程序在其父程序沒有呼叫wait 或waitp程式設計客棧id 的情況下退出,這個子程序就是殭屍程序。如果其父程序還存在而一直不呼叫wait,則該殭屍程序將無法 等到其父程序退出後該程序將被init 執行結果 2 孤兒程序 乙個父程序退出,而它的乙個或多個子程序還在執行,那麼那些子...

殭屍程序和如何刪除殭屍程序

當乙個子程序結束後,他的父程序沒有等待他 wait waitpid 清除他的所有資源時,它就變成乙個殭屍程序。在linux系統中,在每個程序退出的時候,核心釋放該程序所有的資源,包括開啟的檔案,占用的記憶體等。但是仍然為其保留一定的資訊 包括程序號the process id,退出狀態the ter...