linux 進程狀態淺析 可執行 不可中斷的睡眠 暫停 殭屍進程 退出 進程狀態變遷 進程的初始

                                          粉絲不過W 

    衆所周知,現在的分時操作系統能夠在一個 CPU 上運行多個程序,讓這些程序表面上看起來是在同時運行的

     在 linux 系統中,每個被運行的程序實例對應一個或多個進程

    linux 內核需要對這些進程進行管理,以使它們在系統中“同時”運行

    linux 內核對進程的這種管理分兩個方面:進程狀態管理,和進程調度

    進程狀態

         在 linux 下,通過 ps 命令我們能夠查看到系統中存在的進程,以及它們的狀態:

            R (TASK_RUNNING),可執行狀態

               只有在該狀態的進程纔可能在 CPU 上運行

              同一時刻可能有多個進程處於可執行狀態,這些進程的 task_struct 結構(進程控制塊)被放 入對應 CPU 的可執行隊列中(一個進程最多隻能出現在一個 CPU 的可執行隊列中)

              進程調度器的任務就是 從各個 CPU 的可執行隊列中分別選擇一個進程在該 CPU 上運行

              只要可執行隊列不爲空,其對應的 CPU 就不能偷懶,就要執行其中某個進程,一般稱此時的 CPU“忙碌”,對應的, CPU“空閒”就是指其對應的 可執行隊列爲空,以致於 CPU 無事可做

            而 死循環程序基本上總是處於 TASK_RUNNING 狀態(進程處於可執行隊列中)

           除非一 些非常極端情況(比如系統內存嚴重緊缺,導致進程的某些需要使用的頁面被換出,並且在頁面需要換入時又無法分配到內存……),否則這個進程不會睡眠。所以 CPU 的可執行隊列總是不爲空(至少有這麼個進程存在), CPU 也就不會“空閒”

           許多 將正在 CPU 上執行的進程定義爲 RUNNING 狀態、而 將可執行但尚未被調度執行的進程 定義爲 READY 狀態,這兩種狀態 在 linux 下統一爲 TASK_RUNNING 狀態

      S (TASK_INTERRUPTIBLE),可中斷的睡眠狀態:

         處於這個狀態的進程因爲等待某某事件的發生(比如等待 socket 連接、等待信號量),而被掛起

         這些進程的 task_struct 結構被放入 對應事件的等待隊列中,當這些事件發生時(由外部中斷觸發、或由其他進程觸發),對應的等待隊列中的一個或多個進程將被喚醒

         通過 ps 命令我們會看到,一般情況下,進程列表中的絕大多數進程都處於 TASK_INTERRUPTIBLE 狀態(除非機器的負載很高)

      D (TASK_UNINTERRUPTIBLE),不可中斷的睡眠狀態:

         與 TASK_INTERRUPTIBLE 狀態類似,進程處於睡眠狀態,但是此刻進程是不可中斷的

         不可中斷,指的並不是 CPU 不響應外部硬件的 中斷,而是指進程不響應異步信號

         絕大多數情況下,進程處在睡眠狀態時,總是應該能夠響應異步信號的

        kill -9 竟然殺不死一個正在睡眠的進程了,所以 ps 命令看到的進程幾乎不會出現 TASK_UNINTERRUPTIBLE 狀態,而總是 TASK_INTERRUPTIBLE 狀態

     TASK_UNINTERRUPTIBLE 狀態存在的意義:內核的某些處理流程是不能被打斷

         如 響應異步信號,程序的執行流程中就 會被插入一段用於處理異步信號的流程(這個插入的流程可能只存在於內核態,也可能延伸到用戶態),於是原有的流程就被中斷了

      進程對某些硬件進行操作時(如 進程調用 read 系統調用對某個設備文件進行讀操作,而read 系統調用最終執行到對應設備驅動的代碼,並與 對應的物理設備進行交互),可能需要使用TASK_UNINTERRUPTIBLE 狀態對進程進行保護,以避免進程與設備交互的過程被打斷,造成設備陷入不可控的狀態

       這種情況下的 TASK_UNINTERRUPTIBLE 狀態總是非常短暫的,通過 ps 命令捕捉不到

     linux 系統中也存在容易捕捉的 TASK_UNINTERRUPTIBLE 狀態:

           如 執行 vfork 系統調用後,父進程將進入 TASK_UNINTERRUPTIBLE 狀態,直到子進程調用 exit 或 exec

         下面的代碼 能得到處於 TASK_UNINTERRUPTIBLE 狀態的進程:

#include <unistd.h>

void main()
{
    if(!vfork())
    {
        sleep(100);
    }
}

  不管 kill 還是 kill -9,這個TASK_UNINTERRUPTIBLE 狀態的父進程依然屹立不倒

    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 狀態

    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 系統調用創建子進程時,可以設置這個信號

    只要父進程不退出,這個殭屍狀態的子進程就一直存在

    當進程退出的時候,會將它的所有子進程都託管給別的進程(使之成爲別的進程的子進程)

    可能退出進程所在進程組的下一個進程(如 果存在的話),或者是 1 號進程。所以每個進程、每時每刻都有父進程存在。除非它是 1 號進程

     1 號進程, pid 爲 1 的進程,又稱 init 進程

   linux 系統啓動後,第一個被創建的用戶態進程就是 init 進程:

         執行系統初始化腳本,創建一系列的進程(它們都是 init 進程的子孫)

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

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

   X (TASK_DEAD - EXIT_DEAD),退出狀態,進程即將被銷燬

        進程在退出過程中也可能不會保留它的 task_struct。比如這個進程是多線程程序中被 detach 過的進程

       或者父進程通過設置 SIGCHLD 信號的handler 爲 SIG_IGN,顯式的忽略了 SIGCHLD 信號(這是 posix 的規定,儘管子進程的退出信號可以被設置爲 SIGCHLD 以外的其他信號)

     進程將被置於 EXIT_DEAD 退出狀態,這意味着接下來的代碼立即就會將該進程徹底釋放。所以 EXIT_DEAD 狀態是非常短暫的,幾乎不可能通過 ps 命令捕捉到

 

  進程的初始狀態

     進程是通過 fork 系列的系統調用(fork、 clone、 vfork)來創建的,內核(或內核模塊)也可以通過 kernel_thread 函 數創建內核進程

     這些創建子進程的函數本質上都完成了相同的功能——將調用進程複製一份,得到子進程。(可以通過選項參數來決定各種資源是共享、還是私有)

   進程狀態變遷

      進程自創建以後,狀態可能發生一系列的變化,直到進程退出

     進程狀態有好幾種,但是進程狀態的變遷卻只有兩個方向——從 TASK_RUNNING 狀態變爲非 TASK_RUNNING 狀態、或者從非TASK_RUNNING 狀態變爲 TASK_RUNNING 狀態

 

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

   進程從 TASK_RUNNING 狀態變爲非 TASK_RUNNING 狀態:

       響應信號而進入 TASK_STOPED 狀態、或 TASK_DEAD 狀態

       執行系統調用主動進入 TASK_INTERRUPTIBLE 狀態(如 nanosleep 系統調用)、或TASK_DEAD 狀態(如 exit 系統調用);或由於執行系統調用需要的資源得不到滿足,而進入TASK_INTERRUPTIBLE 狀態或 TASK_UNINTERRUPTIBLE 狀態 (如 select 系統調用)

     只能發生在進程正在CPU上執行的情況

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章