"點我查看秘籍連載" 進程狀態以及狀態轉換 進程並非總是處於運行中,例如CPU沒運行在它身上時它就是非運行的。進程在創建之後會改變狀態,不同的狀態之間可以實現狀態切換,可以通過ps或top等命令捕獲進程的狀態。包含以下幾種狀態: 創建態(new):進程正在被創建中,過程非常短暫,用戶無法捕捉 運行態 ...
進程狀態以及狀態轉換
進程並非總是處於運行中,例如CPU沒運行在它身上時它就是非運行的。進程在創建之後會改變狀態,不同的狀態之間可以實現狀態切換,可以通過ps或top等命令捕獲進程的狀態。包含以下幾種狀態:
- 創建態(new):進程正在被創建中,過程非常短暫,用戶無法捕捉
- 運行態(running):進程正在執行中,即CPU正在該進程上
- 就緒態(ready):進程已經準備好可以運行,存放在就緒隊列中等待被調度,操作系統調度時將從就緒隊列中選擇下一個要運行的進程
- 阻塞態(waiting/sleeping/blocking):也稱為睡眠態,進程因為某些原因(如等待IO完成或等待其它事件發生)停止了、睡眠了、阻塞了,CPU轉讓出去,調度時也不會調度到它
- 終止態(terminated):進程執行完成或發生某種特殊事件,進程將退出,但還未被內核清理(顯然,如果已被清理,任何手段都無法捕獲到該進程的信息),這就是終止狀態,也是僵屍態(Zombie或Defunct)
進程在發生某些事件之後會改變自己的狀態,各狀態之間的轉換方式如圖:(如果不好理解,可參考稍後的示例分析)
其中:
上面的狀態轉換中,主要關註的是運行態、就緒態、睡眠態這3者之間的轉換。
運行態轉為就緒態表示當前正在執行的進程已經耗盡了分配給它的時間片,只能交出CPU的控制權,自己進入到就緒隊列等待下次被調度選中後繼續運行。
就緒態轉為運行態表示調度器從就緒隊列中調度進程時,正好選中了該進程,於是該進程將獲取到CPU並開始執行。
運行態轉為睡眠態一般是等待某事件的出現,在事件出現之前,進程無法繼續執行,只能先暫停進入到睡眠態,例如等待信號通知、等待IO完成。信號通知很容易理解,而對於IO等待(比如想要從磁碟文件中讀取一行數據,等待用戶敲下一個字元,等待數據全部輸出到終端屏幕等等),假如在發生IO等待的時候進程繼續保持運行態,它必將繼續持有CPU直到IO的完成,但這個時候的進程根本沒有繼續向下執行,而是完全處於無謂的等待中, CPU在這時候也沒有做任何事情,處於空轉狀態,由於IO操作相比於CPU來說是非常慢的,而CPU是極其珍貴的資源,不能隨意浪費,所以出現IO等待的進程都應該進入阻塞狀態,交出CPU讓它去處理其它進程。
睡眠態轉為就緒態表示睡眠的進程所等待的事件已經發生了,這個睡眠的進程已經可以繼續執行了,於是內核喚醒該進程,將其放入到就緒隊列中等待下次被調度到繼續執行。
註意沒有"就緒 -> 睡眠"和"睡眠 -> 運行"的狀態切換,這很容易理解。不存在"就緒 -> 睡眠"是因為就緒態的進程本就是停止的,當然不可能發生因等待某些事件而進入到睡眠態,只有正在運行中的進程才可能會需要等待某些事件才進入睡眠態。不存在"睡眠 -> 運行"是因為調度器只會從就緒隊列中挑出下一次要運行的進程,所以睡眠態的進程等待的事件完成後,也必須先放入到就緒隊列中,才能等待被調度執行。
關於進程的狀態,還有幾點需要說明。
- 睡眠態是一個非常寬泛的概念,分為可中斷睡眠(interruptiable sleep)和不可中斷睡眠(un-interruptiable sleep)
- 可中斷睡眠是允許接收外界信號和內核信號而被喚醒的睡眠,絕大多數睡眠都是可中斷睡眠,能ps或top捕捉到的睡眠也幾乎總是可中斷睡眠;
- 不可中斷睡眠只能由內核發起信號來喚醒,外界無法通過信號來喚醒,只能在事件完成後由內核喚醒,主要表現在和硬體交互的時候。例如cat一個文件時需要從硬碟上載入數據到記憶體中,在和硬體交互的那段時間一定是不可中斷的睡眠,否則在載入數據的時候突然被人為發送的信號手動喚醒,而被喚醒時和硬體交互的過程又還沒完成,那麼即使喚醒了也沒法將cpu交給它運行。而且,不可中斷睡眠若能被人為喚醒,更嚴重的後果是硬體崩潰。由此可知,不可中斷睡眠是為了保護某些重要進程,也是為了讓cpu不被浪費。
- 可中斷睡眠是允許接收外界信號和內核信號而被喚醒的睡眠,絕大多數睡眠都是可中斷睡眠,能ps或top捕捉到的睡眠也幾乎總是可中斷睡眠;
- 終止態表示的是進程已被終止(比如已經執行完了所有任務),但是內核還沒有將這個進程從內核表項中清理掉。所以,終止態是進程消失前的一個狀態,因為如果進程已被內核清理,任何手段都無法去捕獲該進程的信息。這個狀態其實就是常說的僵屍態,這個狀態是非常重要的狀態,後文還會詳細介紹僵屍進程。
- 除了上面描述的幾種狀態,通常還有一種狀態稱為stopped狀態,它也是一種睡眠態進程,只是比較特殊:它可以通過信號手動喚醒然後繼續運行,所以它是一種可中斷睡眠狀態。之所以要從睡眠態中區分出stopped狀態,主要是為shell的作業提供一種控制手段的。例如,可以按下ctrl+z讓前臺運行的命令進入到Stopped狀態,因為進入到了stopped狀態就是進入了睡眠態,所以放棄CPU,該進程自然就進入到後臺,這其實是發送了SIGTSTP信號給該進程(所以也可以通過kill命令手動發送該信號給進程使其進入stopped狀態);另外,對stopped狀態的進程可以手動發送SIGCONT信號,使其從stopped狀態恢復,也就是喚醒該進程,使其進入到就緒隊列等待被調度繼續執行,此外,shell作業控制的兩個命令fg和bg命令其實在內部都會發送SIGCONT信號。關於信號和shell的作業控制,後面的文章會詳細介紹。
示例分析進程轉換
前面說了一大段關於進程的狀態已經進程狀態轉換的理論,現在用一個簡單的示例分析一下,這個示例如果瞭解fork和exec會更容易理解,不知道也沒關係,稍後會介紹。
假如,在命令行下執行一個"cat a.log"命令。
首先,shell(比如bash)需要解析命令行,比如檢查命令行語法,命令行解析完成後(通過fork())創建一個新的進程(bash進程的子進程),併為其分配好記憶體,進程在創建過程中處於創建態,創建完成後立即放入就緒隊列中成為就緒態進程,此時進程還不叫cat進程,而是子bash進程。
當調度到該進程後,進程轉變為運行態,它將(通過exec函數)調用cat程式將其載入到記憶體中覆蓋替換子bash進程,這個時候的進程才叫做cat進程,於是cat進程開始執行。
cat進程執行時,發現要讀取文件,但是cat進程是用戶模式下的進程,它沒有許可權打開文件,於是通過open()系統調用請求操作系統幫忙打開,於是陷入到內核,內核進程幫忙打開文件後返回一個文件描述符給cat進程併進入用戶模式下,cat進程通過該文件描述符讀取a.log文件,但是當它開始讀數據的時候,cat仍然無許可權讀取文件數據,於是通過read()系統調用請求操作系統幫忙讀取,操作系統將讀取到的數據放入記憶體,然後回到cat進程,cat進程直接從記憶體中讀取數據,並將讀取到的數據輸出到終端屏幕,但是cat進程仍然沒有許可權執行寫終端硬體(Linux下設備也是文件),於是又發送write()系統調用請求操作系統幫忙寫數據到終端,於是數據顯示在屏幕上。
上面的過程中,讀取磁碟文件數據、輸出數據到屏幕都是速度非常慢的IO操作,cat進程都將在這些過程發生時(即IO等待時)進入到不可中斷睡眠狀態,當IO完成時cat進程將進入就緒態,當再次調度到cat進程時,cat進程將轉為運行態。
當輸出數據完成後,cat進程將終止退出,於是進入終止態或者僵屍態等待內核清理該進程,直到內核清理該進程後,cat進程將永久的消失。