1. 程式人生 > >Linux程序管理之狀態(二)

Linux程序管理之狀態(二)

二、程序的生命週期

程序是一個動態的實體,所以他是有生命的。從建立到消亡,是一個程序的整個生命週期。在這個週期中,程序可能會經歷各種不同的狀態。一般來說,所有程序都要經歷以下的3個狀態:

  1. 就緒態。指程序已經獲得所有所需的其他資源,正在申請處理處理器資源,準備開始執行。這種情況下,稱程序處於就緒態。
  2. 阻塞態。指程序因為需要等待所需資源而放棄處理器,或者程序本不擁有處理器,且其他資源也沒有滿足,從而即使得到處理器也不能開始執行。這種情況下,程序處於阻塞態。阻塞狀態也稱休眠狀態或者等待狀態。
  3. 執行態。程序得到了處理器,並不需要等待其他任何資源,正在執行的狀態,稱之為執行態。只有在執行態時,程序才可以使用所申請到的資源。

在Linux系統中,將各種狀態進行了重新組織,由此得到了Linux程序的幾個狀態:

  • RUNNING:正在執行或者在就緒佇列中等待執行的程序。也就是上面提到的執行態和就緒態程序的綜合。一個程序處於RUNNING狀態,並不代表他一定在被執行。由於在多工系統中,各個就緒程序需要併發執行,所以在某個特定時刻,這些處於RUNNING狀態的程序之中,只有一個能得到處理器,而其他程序必須在一個就緒佇列中等待。即使是在多處理器的系統中,Linux也只能同時讓一個處理器執行任務。
  • UNINTERRUPTABLE:不可中斷阻塞狀態。處於這種狀態的程序正在等待佇列中,當資源有效時,可由作業系統進行喚醒,否則,將一直處於等待狀態。
  • INTERRUPTABLE:可中斷阻塞狀態。與不可中斷阻塞狀態一樣,處於這種狀態的程序在等待佇列中,當資源有效時,可以有作業系統進行喚醒。與不可中斷阻塞狀態有所區別的是,處於此狀態中的程序亦可被其他程序的訊號喚醒。
  • STOPPED:掛起狀態。程序被暫停,需要通過其它程序的訊號才能被喚醒。導致這種狀態的原因有兩種。其一是受到相關訊號(SIGSTOP,SIGSTP,SIGTTIN或SIGTTOU)的反應。其二是受到父程序ptrace呼叫的控制,而暫時將處理器交給控制程序。
  • ZOMBIE:殭屍狀態。表示程序結束但尚未消亡的一種狀態。此時程序已經結束執行並釋放掉大部分資源,但尚未釋放程序控制塊。

  image

Linux程序狀態:T (TASK_STOPPED or TASK_TRACED),暫停狀態或跟蹤狀態。

         向程序傳送一個SIGSTOP訊號,它就會因響應該訊號而進入TASK_STOPPED狀態(除非該程序本身處於TASK_UNINTERRUPTIBLE狀態而不響應訊號)。(SIGSTOP與SIGKILL訊號一樣,是非常強制的。不允許使用者程序通過signal系列的系統呼叫重新設定對應的訊號處理函式。)          向程序傳送一個SIGCONT訊號,可以讓其從TASK_STOPPED狀態恢復到TASK_RUNNING狀態。

         當程序正在被跟蹤時,它處於TASK_TRACED這個特殊的狀態。“正在被跟蹤”指的是程序暫停下來,等待跟蹤它的程序對它進行操作。比如在gdb中對被跟蹤的程序下一個斷點,程序在斷點處停下來的時候就處於TASK_TRACED狀態。而在其他時候,被跟蹤的程序還是處於前面提到的那些狀態。

         對於程序本身來說,TASK_STOPPED和TASK_TRACED狀態很類似,都是表示程序暫停下來。 而TASK_TRACED狀態相當於在TASK_STOPPED之上多了一層保護,處於TASK_TRACED狀態的程序不能響應SIGCONT訊號而被喚醒。只能等到除錯程序通過ptrace系統呼叫執行PTRACE_CONT、PTRACE_DETACH等操作(通過ptrace系統呼叫的引數指定操作),或除錯程序退出,被除錯的程序才能恢復TASK_RUNNING狀態。

         Linux程序狀態:Z (TASK_DEAD - EXIT_ZOMBIE),退出狀態,程序成為殭屍程序。

         程序在退出的過程中,處於TASK_DEAD狀態。

         在這個退出過程中,程序佔有的所有資源將被回收,除了task_struct結構(以及少數資源)以外。於是程序就只剩下task_struct這麼個空殼,故稱為殭屍。          之所以保留task_struct,是因為task_struct裡面儲存了程序的退出碼、以及一些統計資訊。而其父程序很可能會關心這些資訊。比如在shell中,$?變數就儲存了最後一個退出的前臺程序的退出碼,而這個退出碼往往被作為if語句的判斷條件。          當然,核心也可以將這些資訊儲存在別的地方,而將task_struct結構釋放掉,以節省一些空間。但是使用task_struct結構更為方便,因為在核心中已經建立了從pid到task_struct查詢關係,還有程序間的父子關係。釋放掉task_struct,則需要建立一些新的資料結構,以便讓父程序找到它的子程序的退出資訊。

         父程序可以通過wait系列的系統呼叫(如wait4、waitid)來等待某個或某些子程序的退出,並獲取它的退出資訊。然後wait系列的系統呼叫會順便將子程序的屍體(task_struct)也釋放掉。          子程序在退出的過程中,核心會給其父程序傳送一個訊號,通知父程序來“收屍”。這個訊號預設是SIGCHLD,但是在通過clone系統呼叫建立子程序時,可以設定這個訊號。

         通過下面的程式碼能夠製造一個EXIT_ZOMBIE狀態的程序:

 
  
  
#include     
void main() {    
if (fork())    
while(1) sleep(100);    
} 
 
 

編譯執行,然後ps一下:

  1. [email protected]:~/test$ ps -ax | grep a\.out 
  2. 10410 pts/0    S+     0:00 ./a.out 
  3. 10411 pts/0    Z+     0:00 [a.out]  
  4. 10413 pts/1    S+     0:00 grep a.out

         只要父程序不退出,這個殭屍狀態的子程序就一直存在。那麼如果父程序退出了呢,誰又來給子程序“收屍”? 當程序退出的時候,會將它的所有子程序都託管給別的程序(使之成為別的程序的子程序)。託管給誰呢?可能是退出程序所在程序組的下一個程序(如果存在的話),或者是1號程序。所以每個程序、每時每刻都有父程序存在。除非它是1號程序。

1號程序,pid為1的程序,又稱init程序。 linux系統啟動後,第一個被建立的使用者態程序就是init程序。它有兩項使命: 1、執行系統初始化指令碼,建立一系列的程序(它們都是init程序的子孫); 2、在一個死迴圈中等待其子程序的退出事件,並呼叫waitid系統呼叫來完成“收屍”工作; init程序不會被暫停、也不會被殺死(這是由核心來保證的)。它在等待子程序退出的過程中處於TASK_INTERRUPTIBLE狀態,“收屍”過程中則處於TASK_RUNNING狀態。

         Linux程序狀態:X (TASK_DEAD - EXIT_DEAD),退出狀態,程序即將被銷燬。

         而程序在退出過程中也可能不會保留它的task_struct。比如這個程序是多執行緒程式中被detach過的程序(程序?執行緒?參見《linux執行緒淺析》)。或者父程序通過設定SIGCHLD訊號的handler為SIG_IGN,顯式的忽略了SIGCHLD訊號。(這是posix的規定,儘管子程序的退出訊號可以被設定為SIGCHLD以外的其他訊號。)          此時,程序將被置於EXIT_DEAD退出狀態,這意味著接下來的程式碼立即就會將該程序徹底釋放。所以EXIT_DEAD狀態是非常短暫的,幾乎不可能通過ps命令捕捉到。

         程序的初始狀態

         程序是通過fork系列的系統呼叫(fork、clone、vfork)來建立的,核心(或核心模組)也可以通過kernel_thread函式建立核心程序。這些建立子程序的函式本質上都完成了相同的功能——將呼叫程序複製一份,得到子程序。(可以通過選項引數來決定各種資源是共享、還是私有。)          那麼既然呼叫程序處於TASK_RUNNING狀態(否則,它若不是正在執行,又怎麼進行呼叫?),則子程序預設也處於TASK_RUNNING狀態。          另外,在系統呼叫呼叫clone和核心函式kernel_thread也接受CLONE_STOPPED選項,從而將子程序的初始狀態置為 TASK_STOPPED。

         程序狀態變遷

         程序自建立以後,狀態可能發生一系列的變化,直到程序退出。而儘管程序狀態有好幾種,但是程序狀態的變遷卻只有兩個方向——從TASK_RUNNING狀態變為非TASK_RUNNING狀態、或者從非TASK_RUNNING狀態變為TASK_RUNNING狀態。          也就是說,如果給一個TASK_INTERRUPTIBLE狀態的程序傳送SIGKILL訊號,這個程序將先被喚醒(進入TASK_RUNNING狀態),然後再響應SIGKILL訊號而退出(變為TASK_DEAD狀態)。並不會從TASK_INTERRUPTIBLE狀態直接退出。

         程序從非TASK_RUNNING狀態變為TASK_RUNNING狀態,是由別的程序(也可能是中斷處理程式)執行喚醒操作來實現的。執行喚醒的程序設定被喚醒程序的狀態為TASK_RUNNING,然後將其task_struct結構加入到某個CPU的可執行佇列中。於是被喚醒的程序將有機會被排程執行。

         而程序從TASK_RUNNING狀態變為非TASK_RUNNING狀態,則有兩種途徑: 1、響應訊號而進入TASK_STOPED狀態、或TASK_DEAD狀態; 2、執行系統呼叫主動進入TASK_INTERRUPTIBLE狀態(如nanosleep系統呼叫)、或TASK_DEAD狀態(如exit系統呼叫);或由於執行系統呼叫需要的資源得不到滿足,而進入TASK_INTERRUPTIBLE狀態或TASK_UNINTERRUPTIBLE狀態(如select系統呼叫)。          顯然,這兩種情況都只能發生在程序正在CPU上執行的情況下。

 

引自:

https://www.cnblogs.com/youngerchina/p/5624580.html

https://www.cnblogs.com/chenglei/archive/2009/11/20/1606881.html