進程狀態
- 進程狀態的簡要介紹
- 運行狀態
- 進程排隊
- 阻塞狀態
- 掛起狀態
- Linux中的進程狀態
進程狀態的簡要介紹
??進程狀態指的是一個操作系統中正在運行的進程當前所處的狀態。根據不同的操作系統,進程狀態可能會有一些細微的差別,但最主要的是以下三種狀態
- 運行狀態
- 阻塞狀態
- 掛起狀態
??下面我會對這三個狀態做一個詳細的介紹
運行狀態
??談到運行狀態我們常常會有一個誤區,那就是認為一個進程在運行中才能叫做運行狀態,其實不然,只要是進程在運行中或者是在運行隊列中都可以被稱作當前進程處于運行狀態
??那么此時又有了一個新的概念:運行隊列,什么是運行隊列?簡單來說,運行隊列就是操作系統中用來存儲和管理進程的數據結構,通過該隊類可以充分利用 CPU 資源并提高系統的響應速度。也可以這么認為,這么運行隊列和我們平常認識的隊列特殊就在于運行隊列中裝的都是進程,并且這個運行隊列是專門為CPU所服務的。
??我們想要運行進程,就需要CPU去參與運算,但是進程有很多,CPU卻只很少,大部分的電腦上只有一個CPU,所以為了高效率管理,操作系統就會幫CPU維護一個運行隊列,當我們想要加載一個進程時,首先操作系統就會讓這個進程的pcb對象去CPU的運行隊列中排隊,這個時候,有牽扯出了一個新的概念:進程排隊
進程排隊
??通過進程的初步認識一和進程的初步認識二我們知道,所謂的pcb對象,實際上就是一個結構體對象,所以,進程排隊,我們就可以簡單理解為將一個結構體對象push到隊列中。
??了解到了以上之后,我們再來看看這個pcb
對象是如何push到隊列中的
??可以看到,在pcb
中嵌套了一個struct listnode
類型的結構體對象,這個結構體對象的next
指針指向的就是下一個結構體對象中的類型為struct listnode
的結構體對象,并且一個pcb
對象中包含了不止一個struct listnode
的結構體對象,這樣就確保了該pcb
對象可以同時被聯入不同的數據結構中。
補充
??雖然這樣的結構設計可以確保pcb同時在操作系統中的不同的數據結構中,但是next
指針和prev
指針始終只能指向下一個pcb
對象中的struct listnode
類型的結構體對象,我們找到pcb
對象的主要目的就是訪問pcb對象的數據,通過這種方法卻只能訪問pcb
對象中的某個成員變量,那要怎么去訪問整個pcb
對象呢?
??我們可以簡化以下這個問題
struct A
{char x;int c;
};int main()
{struct A s;return 0;
}
??以該結構體為例,當我們知道了s的成員變量c的地址,要想知道結構體對象s的地址,通過結構體的內存對齊規則可以得出&c=&s+偏移量
,現在拿到了c的地址,要知道s的地址,也就是&s=&c-偏移量
,我們只需要知道偏移量即可
??要知道偏移量,我們可以對零地址(地址為0)進行強轉成struct A
類型,然后對拿到的成員變量n進行取地址操作,由于結構體對象的地址為0,所以變量n的地址就是n相對于結構體對象地址的偏移量
??同理,當我們知道了pcb
對象中的struct listnode
類型的結構體對象的地址,我們也可以進行如上操作,即&s=(task_struct*)(&c-&((task_struct*)0->c))
阻塞狀態
??首先我們要知道,進程不是一直都在運行的,有時候進程需要等待某種硬件資源,比如在C語言程序中,我們會用到printf函數和scanf函數,printf函數就需要用到屏幕,而scanf就需要鍵盤,如果說進程沒有等到對應的硬件資源時,它就不會繼續運行。而是會做兩件事
- 將當前進程狀態由運行狀態改為阻塞狀態
- 將pcb對象連到對應資源的等待隊列中
??通過以上我們可以知道,不是只有CPU才有隊列,其他的設備也有自己的隊列。
??當一個程序中有scanf函數需要用到鍵盤,CPU此時無法繼續執行下去,操作系統就會將該程序對應進程pcb從CPU的運行隊列拿到鍵盤的等待隊列中去,當我們通過鍵盤按下一個按鍵的時候,操作系統會將該數據拿到并交給對應的進程,然后再將pcb從鍵盤的等待隊列中拿到CPU的運行隊列中去
掛起狀態
??掛起狀態的前提就是當前計算機的資源(主要是內存資源)已經十分吃緊了,而有個進程還在等待隊列中(阻塞狀態)
??也就是說當前進程無法繼續執行下去并且該進程對應的可執行程序仍然會占用內存空間,操作系統為了節省資源,就會將該進程的對應的程序和數據寫入磁盤中,這個時候該進程就是掛起狀態。等到該進程要再次被調度的時候操作系統再將對應的數據和程序加載到內存
補充
??操作系統一般會將進程所對應的代碼和數據放到磁盤中的一塊固定的區域,這塊區域叫做swap分區,這個區域是操作系統在資源緊張的時候和磁盤進行數據交換的地方
??一般來說,swap分區不會太大,通常是內存大小的一般或者是等于內存大小,最多不會超過內存大小的兩倍,因為喚入和喚出其實是一個訪問外設的過程(將數據進行拷貝),這是比較慢的,用效率換取系統的穩定性,如果將swap分區設置的比較大,那么操作系統就會比較依賴這個swap分區最終帶來的結果就是整個系統和swap分區IO交互的頻率變高,使得系統的效率就變低了
Linux中的進程狀態
??那么,進程狀態究竟是什么呢?實際上進程狀態實際上就是整形變量,就像下面這樣
/*
* The task state array is a strange "bitmap" of
* reasons to sleep. Thus "running" is zero, and
* you can test for combinations of others with
* simple bit tests.
*/
static const char* const task_state_array[] = {
"R (running)", /* 0 */
"S (sleeping)", /* 1 */
"D (disk sleep)", /* 2 */
"T (stopped)", /* 4 */
"t (tracing stop)", /* 8 */
"X (dead)", /* 16 */
"Z (zombie)", /* 32 */
};
??以上為狀態在kernel原代碼中的定義。
R 運行狀態:
??運行狀態并不代表著進程正在運行中,它表明進程要么是在運行中要么在運行隊列里隨時做好了被調度的準備
補充:前臺進程和后臺進程
Linux中,前臺進程和后臺進程是指進程與終端(Terminal)之間的關系。
前臺進程:前臺進程是指當前正在終端上活躍運行的進程,它會接收終端輸入并向終端輸出信息。用戶可以直接與前臺進程進行交互,而且前臺進程通常會占用終端的顯示區域。當一個進程在前臺運行時,用戶可以通過鍵盤輸入命令來控制該進程的行為。
后臺進程:后臺進程是指在終端上運行,但不會占用終端顯示區域或接收終端輸入的進程。用戶可以將一個前臺進程轉為后臺進程,從而讓其在后臺靜默運行,不影響用戶在終端上的其他操作。后臺進程通常用于執行一些需要長時間運行的任務,或者不需要用戶直接交互的程序。
??T在Linux中,可以使用特定的命令和符號來控制進程的前臺和后臺運行。例如,使用 & 符號可以將一個命令放到后臺運行;使用 fg 命令可以將一個后臺進程切換到前臺運行。這些功能使得用戶能夠更靈活地管理和控制系統中的進程。
S 睡眠狀態:
??睡眠狀態意味著在等待某個事件的完成(這個睡眠也叫做可中斷睡眠),當我們在C語言文件中加入sleep函數也可以使進程變為睡眠狀態
??在以上代碼中,因為進程一直在等待從鍵盤中拿數據,所以該進程處于鍵盤的等待隊列中,此時的狀態就是直接講到的阻塞狀態,在Linux中,將其之為睡眠狀態
D 磁盤休眠狀態
??有時候也叫不可中斷睡眠狀態,在這個狀態下的進程通常會等待IO的結束(操作系統在內存嚴重不足時會殺死進程,但是不會中斷狀態為D的進程)
T 停止狀態
??T停止狀態(stopped): 可以通過發送 SIGSTOP 信號給進程來停止(T)進程。這個被暫停的進程可以通過發送 SIGCONT 信號讓進程繼續運行。
注意:一旦將一個進程停止,這個進程就變成了后臺進程
X死亡狀態
這個狀態只是一個返回狀態,你不會在任務列表里看到這個狀態