天天看點

Linux系統之程序狀态一、程序狀态二、程序狀态轉換三、程序排程

一、程序狀态

  • D:uninterruptible sleep (usually IO)
  • R:running or runnable (on run queue)
  • S:interruptible sleep (waiting for an event to complete)
  • T:stopped by job control signal
  • t:stopped by debugger during the tracing
  • W:paging (not valid since the 2.6.xx kernel)
  • X:dead (should never be seen)
  • Z:defunct ("zombie") process, terminated but not - reaped by its parent

1、R (TASK_RUNNING),可執行狀态

隻有在該狀态的程序才可能在CPU上運作。而同一時刻可能有多個程序處于可執行狀态,這些程序的task_struct結構(程序控制塊)被放入對應CPU的可執行隊列中(一個程序最多隻能出現在一個CPU的可執行隊列中)。進而,程序排程器就從各個CPU的可執行隊列中分别選擇一個程序在該CPU上運作。

很多作業系統教科書将正在CPU上執行的程序定義為RUNNING狀态、而将可執行但是尚未被排程執行的程序定義為READY狀态,這兩種狀态在linux下統一為 TASK_RUNNING狀态。

2、S (TASK_INTERRUPTIBLE),可中斷的睡眠狀态

處于這個狀态的程序因為等待某某事件的發生(比如等待socket連接配接、等待信号量),而被挂起。這些程序的task_struct結構(程序控制塊)被放入對應事件的等待隊列中。當這些事件發生時(由外部中斷觸發、或由其他程序觸發),對應的等待隊列中的一個或多個程序将被喚醒。

通過ps指令會看到,一般情況下,程序清單中的絕大多數程序都處于TASK_INTERRUPTIBLE狀态(除非機器的負載很高)。畢竟CPU就這麼幾個,程序動辄幾十上百個,如果不是絕大多數程序都在睡眠,CPU又怎麼響應得過來。

3、D (TASK_UNINTERRUPTIBLE),不可中斷的睡眠狀态

與TASK_INTERRUPTIBLE狀态類似,程序處于睡眠狀态,但是此刻程序是不可中斷的。不可中斷,指的并不是CPU不響應外部硬體的中斷,而是指程序不響應異步信号。

絕大多數情況下,程序處在睡眠狀态時,總是應該能夠響應異步信号的。否則你将驚奇的發現,kill -9竟然殺不死一個正在睡眠的程序了!于是我們也很好了解,為什麼ps指令看到的程序幾乎不會出現TASK_UNINTERRUPTIBLE狀态,而總是TASK_INTERRUPTIBLE狀态。

而TASK_UNINTERRUPTIBLE狀态存在的意義就在于,核心的某些處理流程是不能被打斷的。如果響應異步信号,程式的執行流程中就會被插入一段用于處理異步信号的流程(這個插入的流程可能隻存在于核心态,也可能延伸到使用者态),于是原有的流程就被中斷了。

例如,在程序對某些硬體進行操作時(比如程序調用read系統調用對某個裝置檔案進行讀操作,而read系統調用最終執行到對應裝置驅動的代碼,并與對應的實體裝置進行互動),可能需要使用TASK_UNINTERRUPTIBLE狀态對程序進行保護,以避免程序與裝置互動的過程被打斷,造成裝置陷入不可控的狀态。這種情況下的TASK_UNINTERRUPTIBLE狀态總是非常短暫的,通過ps指令基本上不可能捕捉到。

4、T/t (TASK_STOPPED or TASK_TRACED),暫停狀态或跟蹤狀态

T (TASK_STOPPED)狀态:向程序發送一個SIGSTOP信号,它就會因響應該信号而進入TASK_STOPPED狀态(除非該程序本身處于TASK_UNINTERRUPTIBLE狀态而不響應信号)。

SIGSTOP與SIGKILL信号一樣,是非常強制的。不允許使用者程序通過signal系列的系統調用重新設定對應的信号處理函數。

向程序發送一個SIGCONT信号(kill -18),可以讓其從TASK_STOPPED狀态恢複到TASK_RUNNING狀态;或者kill -9直接嘗試殺死。

t (TASK_STOPPED)狀态:當程序正在被跟蹤時,它處于TASK_TRACED這個特殊的狀态。“正在被跟蹤”指的是程序暫停下來,等待跟蹤它的程序對它進行操作。比如在gdb(UNIX及UNIX-like下的調試工具)調試中對被跟蹤的程序下一個斷點,程序在斷點處停下來的時候就處于TASK_TRACED狀态。而在其他時候,被跟蹤的程序還是處于前面提到的那些狀态。

對于程序本身來說,TASK_STOPPED和TASK_TRACED狀态很類似,都是表示程序暫停下來。

而TASK_TRACED狀态相當于在TASK_STOPPED之上多了一層保護,處于TASK_TRACED狀态的程序不能響應SIGCONT信号而被喚醒。隻能等到調試程序通過ptrace系統調用執行PTRACE_CONT、PTRACE_DETACH等操作(通過ptrace系統調用的參數指定操作),或調試程序退出,被調試的程序才能恢複TASK_RUNNING狀态。

5、Z (TASK_DEAD - EXIT_ZOMBIE),退出狀态,程序成為僵屍程序

程序在退出的過程中,處于TASK_DEAD狀态。在這個退出過程中,程序占有的所有資源将被回收,除了task_struct結構(以及少數資源)以外。于是程序就隻剩下task_struct這麼個空殼,故稱為僵屍。

之是以保留task_struct,是因為task_struct裡面儲存了程序的退出碼、以及一些統計資訊。而其父程序很可能會關心這些資訊。父程序可以通過wait系列的系統調用(如wait4、waitid)來等待某個或某些子程序的退出,并擷取它的退出資訊(儲存在task_struct裡)。然後wait系列的系統調用會順便将子程序的屍體(task_struct)也釋放掉。

當父/子程序在不同時間點退出時,就可能會出現Z的細分狀态:
  1. 僵屍狀态

    一個程序使用 fork 建立子程序,如果子程序退出後父程序沒有調用 wait 或 waitpid 擷取子程序的狀态資訊,并将子程序釋放掉。那麼子程序的程序描述符仍然儲存在系統中,仍然占用程序表,此時程序就處于僵屍狀态。

    子程序在退出的過程中,核心會給其父程序發送一個信号,通知父程序來“收屍”。出現僵屍狀态可能有兩種情況:

    第一種情況,父程序收到通知還沒來得及完成收屍,此時正常;

    第二種情況,父程序收屍出現異常,此時,隻要父程序不退出,子程序的僵屍狀态就一直存在,可以通過殺死父程序或者重新開機來解決。

  2. 孤兒狀态

    父程序退出,相應的一個或多個子程序還在運作,那麼那些子程序将處于孤兒狀态,成為孤兒程序。這些程序會被托管給别的程序,托管給誰呢?可能是退出程序所在程序組的下一個程序(如果存在的話),或者是1号程序。是以每個程序、每時每刻都有父程序存在。除非它是1号程序。

    1号程序,pid為1的程序,又稱init程序。

    linux系統啟動後,第一個被建立的使用者态程序就是init程序。它有兩項使命:

    1、執行系統初始化腳本,建立一系列的程序(它們都是init程序的子孫);

    2、在一個死循環中等待其子程序的退出事件,并調用waitid系統調用來完成“收屍”工作;

    init程序不會被暫停、也不會被殺死(這是由核心來保證的)。它在等待子程序退出的過程中處于TASK_INTERRUPTIBLE狀态,“收屍”過程中則處于TASK_RUNNING狀态。

6、X (TASK_DEAD - EXIT_DEAD),退出狀态,程序即将被銷毀

程序在退出過程中也可能不會保留它的task_struct。比如這個程序是多線程程式中被detach過的程序。或者父程序通過設定SIGCHLD信号的handler為SIG_IGN,顯式的忽略了SIGCHLD信号。(這是posix的規定,盡管子程序的退出信号可以被設定為SIGCHLD以外的其他信号。)

此時,程序将被置于EXIT_DEAD退出狀态,這意味着接下來的代碼立即就會将該程序徹底釋放。是以EXIT_DEAD狀态是非常短暫的,幾乎不可能通過ps指令捕捉到。

7、檢視程序狀态

程序小狀态:

  • <:high-priority (not nice to other users)
  • N:low-priority (nice to other users)
  • L:has pages locked into memory (for real-time and custom IO)
  • s:is a session leader
  • l:is multi-threaded (using CLONE_THREAD, like NPTL pthreads do)
  • +:is in the foreground process group

常用的指令:ps指令,見Linux系統之常用指令

  • ps -eo stat,pid,user,%cpu,%mem,time,cmd

  • ps -eo stat,pid,user,%cpu,%mem,time,cmd | grep -e '^[R]'

二、程序狀态轉換

Linux系統之程式狀态一、程式狀态二、程式狀态轉換三、程式排程

1、程序初始狀态

程序是通過fork系列的系統調用(fork、clone、vfork)來建立的,核心(或核心子產品)也可以通過kernel_thread函數建立核心程序。這些建立子程序的函數本質上都完成了相同的功能——将調用程序複制一份,得到子程序。(可以通過選項參數來決定各種資源是共享、還是私有。)

那麼既然調用程序處于TASK_RUNNING狀态(否則,它若不是正在運作,又怎麼進行調用?),則子程序預設也處于TASK_RUNNING狀态。

另外,在系統調用調用clone和核心函數kernel_thread也接受CLONE_STOPPED選項,進而将子程序的初始狀态置為 TASK_STOPPED。

2、程序狀态變遷

程序自建立以後,狀态可能發生一系列的變化,直到程序退出。而盡管程序狀态有好幾種,但是程序狀态的變遷卻隻有兩個方向——從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上執行的情況下。

三、程序排程

Linux系統之程式狀态一、程式狀态二、程式狀态轉換三、程式排程