您好,登錄后才能下訂單哦!
本篇內容主要講解“linux進程狀態有哪些”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學習“linux進程狀態有哪些”吧!
linux進程有6種狀態:1、R可執行狀態,只有該狀態的進程才可能在CPU上運行;2、S可中斷的睡眠狀態,處于這個狀態的進程因為等待某某事件的發生,而被掛起;3、D不可中斷的睡眠狀態,進程處于睡眠狀態,但是此刻進程是不可中斷的;4、T暫停狀態或跟蹤狀態,向進程發送一個SIGSTOP信號,它就會因響應該信號而進入T狀態;5、Z僵尸狀態,表示一個進程即將死亡的狀態;6、X死亡狀態。
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 | 暫停狀態或跟蹤狀態, 不可處理 signal, 因為根本沒有時間片運行代碼 |
Z | TASK_DEAD - EXIT_ZOMBIE | 退出狀態,進程成為僵尸進程。不可被 kill, 即不響應任務信號, 無法用 SIGKILL 殺死 |
擴展知識:什么是等待隊列,什么是運行隊列,什么是掛起/阻塞,什么叫喚醒進程
我們把從運行狀態的task_struct(run_queue),放到等待隊列中,就叫做掛起等待(阻塞)從等待隊列,放到運行隊列,被CPU調度就叫做喚醒進程
當一個進程在運行的過程中,由于其某些運行條件還沒就緒(比如要網絡但是網卡了,或者需要等待IO,也就是需要使用外設了),就會被放到等待隊列中,并且task_struct里面的狀態位也會被改變為S/D。
當進程處于S/D狀態的時候,在一個等待隊列里面等著使用外設(比如網卡,磁盤顯示器等)
等CPU的隊列叫做運行隊列,等外設的設備叫做等待隊列
所謂的進程,在運行的時候,有可能因為運行需要,可以會在不同的隊列里
在不同的隊列里,所處的狀態是不一樣的
當一個進程在R狀態的時候,如果需要某種外設,但是外設在被使用,我就把你的狀態變成S/D,然后把你的task_struct放到等待隊列里面去。
到此,相信大家對“linux進程狀態有哪些”有了更深的了解,不妨來實際操作一番吧!這里是億速云網站,更多相關內容可以進入相關頻道進行查詢,關注我們,繼續學習!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。