每天3分鐘作業系統修煉祕籍(24):程序狀態以及狀態轉換
點我檢視祕籍連載
程序狀態以及狀態轉換
程序並非總是處於執行中,例如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不被浪費。
- 終止態表示的是程序已被終止(比如已經執行完了所有任務),但是核心還沒有將這個程序從核心表項中清理掉。所以,終止態是程序消失前的一個狀態,因為如果程序已被核心清理,任何手段都無法去捕獲該程序的資訊。這個狀態其實就是常說的殭屍態,這個狀態是非常重要的狀態,後文還會詳細介紹殭屍程序。
- 除了上面描述的幾種狀態,通常還有一種狀態稱為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程序將永久的消失。