linux進程有6種狀態:1、R可執行狀態,只有該狀態的進程才可能在CPU上運行;2、S可中斷的睡眠狀態,處於這個狀態的進程因為等待某某事件的發生,而被掛起;3、D不可中斷的睡眠狀態,進程處於睡眠狀態,但是此刻進程是不可中斷的;4、T暫停狀態或追蹤狀態,向進程發送一個SIGSTOP訊號,它就會因回應該訊號而進入T狀態;5、Z殭屍狀態,表示一個行程即將死亡的狀態;6、X死亡狀態。
本教學操作環境:linux7.3系統、Dell G3電腦。
在 Linux 中,一個行程有6種狀態,分別是:可執行狀態、可中斷的睡眠狀態、不可中斷的睡眠狀態、暫停狀態或追蹤狀態、殭屍狀態和我死亡狀態。
Linux進程狀態詳解
#R(TASK_RUNNING) 可執行狀態
##只有在該狀態的程序才可能在CPU 上運作。而同一時刻可能有多個進程處於可執行狀態,這些進程的 task_struct 結構(進程控制塊)被放入對應 CPU 的可執行佇列(一個進程最多只能出現在一個 CPU 的執行佇列中)。進程調度器的任務就是從各個 CPU 的可執行佇列中分別選擇一個程序在該 CPU 上執行。 許多作業系統教科書將正在 CPU 上執行的程序定義為 RUNNING 狀態、而將可執行但是尚未被調度執行的進程定義為 READY 狀態,這兩種狀態在 linux 下統一為 TASK_RUNNING 狀態。S(TASK_INTERRUPTIBLE) 可中斷的睡眠狀態
處於這個狀態的進程因為等待某某事件的發生(例如等待socket 連接、等待信號量),而被掛起。這些程序的 task_struct 結構被放入對應事件的等待佇列中。當這些事件發生時(由外部中斷觸發、或由其他程序觸發),對應的等待佇列中的一個或多個程序將會被喚醒。 透過 ps 指令我們會看到,一般情況下,進程清單中的絕大多數行程都處於 TASK_INTERRUPTIBLE 狀態(除非機器的負載很高)。畢竟 CPU 就這麼一兩個,進程動輒幾十上百個,如果不是絕大多數進程都在睡眠,CPU 又怎麼回應得過來。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。T(TASK_STPPED 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 狀態。另外,在系統呼叫呼叫 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 狀態,則有兩種途徑:
回應訊號而進入TASK_STOPED 狀態、或TASK_DEAD狀態;
執行系統呼叫主動進入TASK_INTERRUPTIBLE 狀態(如nanosleep 系統呼叫)、或TASK_DEAD 狀態(如exit 系統呼叫);或由於執行系統呼叫所需的資源無法滿足,而進入TASK_INTERRUPTIBLE 狀態或TASK_UNINTERRUPTIBLE 狀態(如select 系統呼叫)。
顯然,這兩種情況都只能發生在進程正在 CPU 上執行的情況下。
Linux進程狀態說明
#狀態符 | 狀態全名為 | 描述 |
---|---|---|
R | TASK_RUNNING | 可執行狀態&運行狀態(在run_queue 佇列裡的狀態) |
S | TASK_INTERRUPTIBLE | 可中斷的睡眠狀態, 可處理signal |
D | TASK_UNINTERRUPTIBLE | 不可中斷的睡眠狀態, 可處理signal, 有延遲 |
T | TASK_STOPPED or TASK_TRACED | #TASK_STOPPED or TASK_TRACED |
Z | TASK_DEAD - EXIT_ZOMBIE |
##ZTASK_DEAD - EXIT_ZOMBIE
退出狀態,進程變成殭屍行程。不可被kill, 即不響應任務信號, 無法用SIGKILL 殺死
#擴展知識:什麼是等待隊列,什麼是運行隊列,什麼是掛起/阻塞,什麼叫喚醒行程
我們把從運作狀態的task_struct(run_queue),放到等待佇列中,就叫做掛起等待(阻塞)從等待佇列,放到執行佇列,被CPU調度就叫做喚醒進程 當一個進程在運作的過程中,由於其某些運作條件還沒就緒(例如要網路但是網卡了,或者需要等待IO,也就是需要使用周邊了),就會被放到等待佇列中,並且task_struct裡面的狀態位元也會被改變為S/D。 當進程處於S/D狀態的時候,在一個等待佇列裡面等著使用外設(例如網卡,磁碟顯示器等)所謂的進程,在運行的時候,有可能因為運行需要,可以會在不同的隊列裡######在不同的隊列裡,所處的狀態是不一樣的###### 當一個行程在R狀態的時候,如果需要某種外設,但是外設在被使用,我就把你的狀態變成S/D,然後把你的task_struct放到等待隊列裡面去######相關推薦:《###Linux影片教學###》###以上是linux進程有幾種狀態的詳細內容。更多資訊請關注PHP中文網其他相關文章!