線程調度
===============
Windows實現了一個優先順序驅動,搶佔式的調度系統--最高優先順序的可啟動並執行線程會一直運行下去, 線程只能運行在允許它啟動並執行某些處理器上, 這種現象叫做processor affinity. 預設的, 線程可以運行在任意一個available的處理器上, 但是你可以通過Windows scheduling function來設定存在於image header中的affinity mask, 從而修改processor affinity.
當一個線程被選中運行時, 它運行一段時間, 這段時間叫做quantum(配額). quantum是這樣的一段時間, Windows允許線程先運行著, 在這段時間過了之後, Windows就可以中斷線程去做一些其他的事情, 包括判斷是否有跟當前線程同優先順序的或更高優先順序的線程在等待, 和判斷當前線程的優先順序是否需要降低. Quantum值在不同的線程中間是不同的, 甚至在作業系統不同的時候也是不同的. 然而,一個線程可能不會完成它的quantum. 因為Windows 實現了搶佔式調度器(scheduler), 如果另一個線程擁有更高的優先順序, 當它準備好可以啟動並執行時候, 當前啟動並執行iancheng可能會在他的時間片結束之前被奪去cpu. 事實上, 線程甚至可以在開始他的quantum之前, 就被確定稍後再運行, cpu被搶掉.
Windows的線程調度代碼是實現在核心中的. 不存在調度器模組或者函數, 調度代碼是散落在核心中調度相關事件發生的處理中的. 這些執行線程調度的函數合起來被稱為核心調度器(Kernel's dispatcher). 線程分配發生在DPC/dispatch級, 是由以下的任何事件觸發的.
- 一個線程進入可執行狀態(ready to execute), 比如說一個線程剛剛被建立出來, 或者剛剛被從等待狀態中釋放出來.
- 線程離開了運行狀態, 因為他的quantum結束了, 或者因為他terminate了, 或者因為進入了等待狀態了.
- 線程的優先順序更改了, 或者由於系統服務調用, 或者由於Windows自己修改了優先順序的值.
- 運行中的processor affinity修改了
任何一種情形下, Windows都必須決定下一個應該啟動並執行線程. 當Widnows選擇了一個新的線程來啟動並執行時候, 他執行context switch(上下文轉換). 上下文轉換就是一個過程, 該過程儲存與運行中的線程相關的不穩定的機器狀態, 載入另一個線程的這些狀態, 並開始新線程的執行.
很明顯, windows是線上程的粒度上進行分配的. 進程的概念是一個不啟動並執行單位, 僅僅是為它其 中的線程提供上下文和資源, 而這樣的調度安排是符合這樣的概念的. 因為調度的確定是嚴格的建立線上程的基礎上的, 所以調度起來就不需要考慮線程究竟屬於哪個進程. 比如說, 進程A 有10個runnable的線程, 進程B有兩個, 所有十二個線程有同樣的優先順序, 每個線程都會分配到1/12的CPU時間, 而並不會給A 50%, 給B 50%.
中斷優先順序 VS 進程優先順序
======================
前面講過, 線程通常運行在IRQL 0 或者1 上. Windows Debugging之三介紹了IRQL的資訊.
使用者態的線程永遠是運行在IRQL0上的, 即最低等級, 根本就算不上中斷等級, 也可以說沒等級, 任何中斷都可以打斷使用者態線程的執行, 任何使用者態的線程, 不管他的優先順序有多高, 都不可能干擾硬體的中斷.
只有核心態的APC執行在IRQL1的等級上, 因為他們可以任意的打斷使用者態的線程.
核心態啟動並執行線程可以提高它的IRQL等級來進行一些特殊處理, 比如當執行一個涉及到線程分配的系統調用的時候(IRQL 2).
線程狀態
=================
詳細介紹如下:
- Ready---dispatcher在尋找一個線程來執行的時候, 它只考慮線程池中處於ready狀態的線程. 它們僅僅是在等待被執行.
- Standby---處於standby狀態的線程已經被某一個處理器選作下一個被處理的對象了. 當正確的條件達到的時候, dispatcher為這個線程執行內容轉換. 對系統中的每一個處理器而言, 只有一個線程能處於standby的狀態.
- Running---一旦dispatcher為某個線程執行了上下文轉換, 那麼這個線程就進入了running的狀態. 線程的執行回持續下去, 直到以下的情況發生, 1. 核心搶佔了它,以便把資源給更高等級的線程來使用. 2. 他的quantum結束了. 3. 他運行結束了(terminate). 4. 線程自己自願的轉入等待狀態.
- Waiting---一個線程通過以下的幾種途徑進入等待狀態: 1. 自願進入等待狀態, 去等待一個對象來同步化他的執行. 2. 作業系統(比如IO系統)或者環境子系統指導線程來掛起自己. 當線程的等待結束時, 再看他的優先順序來決定該線程要麼進入運行狀態, 要麼回到ready狀態.
- Transition--- 當線程已經準備好,可以被執行的時候, 正巧核心棧的記憶體頁被換出了記憶體, 該線程進入到transition狀態. 當核心棧被帶回記憶體的時候,線程進入到ready狀態.
- Terminated---當一個線程結束了執行, 他就進入到一個結束的狀態(terminated). 一旦結束了, 線程對象也許會被刪掉,也許不會(對象管理器會設定這項政策的).
- Initialized--- 線程建立的時候, 在內部使用的狀態(used internally)
配額(Quantum)
====================
quantum是一段線程啟動並執行一段時間, 在這段時間之後, 作業系統檢查是否有另外的據有同樣優先順序的線程需要進入運行狀態. 如果一個線程完成了他的quantum, 並且沒有其他的線程有跟它一樣高的優先順序, Windows會重新分配給這個線程一個quantum.
每一個線程都有一個quantum值, 該quantum值代表在quantum到期之前, 線程可以運行多久. 這個值並不是時間的長短, 而是一個整形值, 我們稱它為quantum units.
配額的計算
------------------------
線程啟動時,windows xp和windows 2000 professional預設的quantum值是6. 終極目標是最大程度的減小環境切換的時間. 通過一個更長的quantum, 伺服器應用程式有更好的機會來完成用戶端發來的請求, 情切在配額結束之前返回到等待狀態. Windows Server 2003的quantum預設值是36, windows 2000 server是12.
在時鐘中斷的任意時刻, 時鐘中斷處理常式從縣城的quantum中扣除一個定值3. 如果線程的quantum已經沒有了, 那麼線程配額用光的處理常式被啟用, 同時另一線程可能被選中,運行. 因為時鐘中斷每次扣除3, 所以每個線程會預設的運行兩個刻度(XP).
即使系統在DPC/dispatch等級或更高(比如說一個DPC或者一個終端服務程式在執行的時候), 當時鐘中斷髮生, 即使線程沒有運行完畢一整個的刻度, 當前的線程還是會減少他的quantum. 如果減少quantum的動作還沒完成, 裝置就中斷了; 或者DPC發生在刻度計時器中斷之前, 線程可能永遠不會減少它的quantum.
刻度的長度根據不同的硬體系統平台的不同而不同. 時鐘中斷的頻率取決於HAL, 而不是核心. 比如說, 多數的x86單個處理器的刻度是10毫秒, 而多處理器的x86平台是15毫秒.
時鐘tick一下, 我們用3個quantum來表達, 而不是一個的原因是, 這樣可以允許quantum在等待結束的時候衰退掉一部分. 當一個線程擁有一個基礎優先順序低於14, 執行等待函數的時候(WaitForSingleObject or WaitForMultipleObjects), 他的quantum就減少1 quantum單位(優先順序高於14或者就是14的線程,在等待結束之後會重新設定它們的優先順序 reset).
這樣的部分的減少解決如下的情況: 一個線程在時鐘計時器觸發之前就進入了等待狀態. 如果沒有調整的話, 他就可能永遠不會減少他的quantum了. 比如說, 一個線程在運行, 進入了等待狀態, 又運行了, 又進入了等待狀態, 但是每當在時鐘計時器觸發的時候, 都不屬於當前啟動並執行線程, 所以這個線程的quantum就永遠不會減少了.
自願轉換
=============
情境
Windows在面臨問題"誰該得到CPU"的時候, 依靠線程優先順序; 但是實際運作中這是如何做到的呢? 下面的部分就詳細描述基於優先順序的搶佔式多任務系統式如何線上程層級上工作的.
自願轉換
首先, 一個線程可以通過進入等待某個對象的方式, 自願的放棄對於處理器的使用. 比如說事件, 互斥量, 旗語訊號, IO完成連接埠, 進程, 線程, 視窗訊息等等. 進入等待某對象的方式是通過調用某些win32等待函數完成的, 比如WaitForSingleObject 或者WaitForMultipleObjects
自願轉換大致相當於一個線程在快餐桌前點還沒做好的菜一般. 與其佔著點餐的隊伍, 不如站到一邊去, 在大廚做它的漢堡的時候, 讓其他的線程去執行他們的請求. 當漢堡做好了, 第一個線程排到跟它優先順序相同的ready的隊伍的最後面. 無論如何, 多數的等待操作都會導致暫時的優先順序增加, 這樣線程可以在它的漢堡準備好了之後快速的拿走它並開始進食.
中, 最上面的線程自願的放棄了處理器, 所以下一個ready隊列中的線程(腦袋上有光圈兒的那個方塊)可以運行了. 儘管從這張圖上看去, 覺得自願放棄的線程的優先順序降低了, 其實它沒有. 它僅僅是被移到等待的隊列中去了. 那這個線程剩下的quantum怎麼辦呢? quantum值在進入等待狀態的時候並不會被重設, 事實上, 正如早些時候講過的, 當等待滿足的時候, 線程的quantum會減少一個quantum的單位, 等於一個刻度的三分之一(除了優先順序為14或14以上的線程, 他們的quantum在等待後會被重設.)
搶佔
================
在這個情境下, 一個低等級的線程會被高等級的剛剛進入ready狀態的線程給搶掉cpu. 有可能基於如下的原因發生這種情況:
1. 更高優先順序的線程的等待結束了.(這個事件發生在其他線程等待已經發生的時候)
2. 一個線程的優先順序要不增加了,要不減少了.
兩者中的任何一個, 都可以導致高優先順序的線程搶佔CPU.
注意: 運行在核心態的線程可以被使用者態的線程搶佔的. 線程是否運行在核心態跟搶佔無關. 線程的優先順序是做搶佔決定的指標.
當一個線程被搶佔, 他被放在它的優先順序的那個ready隊列的頭部, 這樣當它再次啟動並執行時候, 它就可以結束掉他的quantum.
中, 一個擁有優先順序18的線程從等待狀態恢複過來, 重新獲得了cpu, 引起了一個優先順序為16的線程被打到ready隊列的隊首 注意,是隊首, 而不是隊尾.
配額結束
=================================
當一個線程用光了他的cpu時間片配額, windows必須決定是否減小這個線程的優先順序, 還有是否另個線程應該被安排到處理器上.
如果線程優先順序降低了, Windows尋找一個更合適的線程來安排.(比如說, 一個更適合的線程應該是在ready隊列裡的, 比當前啟動並執行線程的新優先順序擁有更高的優先順序的線程). 如果線程優先順序沒有降低, Widnows選擇ready隊列裡的下一個線程, 然後把先前的線程放到那個隊列的尾部(給它一個新的quantum, 修改它的狀態為ready). 這樣的情形在中有表示出來. 如果沒有其他的相同優先順序的線程處在ready的狀態, 那麼當前線程就再運行一個quantum.
事實上, 線程擁有quantum並不意味著它一定要運行結束那個quantum. 一個線程可能在他的時間片結束之前自願的放棄對於CPU的佔用, 要麼它進入等待狀態, 要麼它被更高優先順序的線程打到ready隊列的頭上去. 如同前面討論的, 如果自願放棄控制CPU, 線程會被在再次啟動並執行時候, 被分配一個新的quantum值. 如果線程被搶佔了, 不管怎樣, 他被挪到相應優先順序的ready隊列的頭上, 晚些時候, 當它再被安排上的時候, 線程就會運行至時間片結束.