Windows線程調度學習(一)

前言

Windows 線程調度器的實現分散在內核各處,而且與許多組件都有關聯,很難進行系統地學習,因此我打算寫幾篇文章來記錄下本身學習過程當中的思考和分析,同時也方便往後查閱,此文能夠看做是《Windows內核原理與實現》中線程調度部分的讀書筆記和簡單總結。數據結構

正文

一. 線程當前狀態


在對調度器函數進行分析學習以前,首先要明確一個概念:調度器只由內核層進行負責實現,不涉及執行體層。所以線程相關的數據結構只有 KTHREAD,其中調度相關的最重要的成員是 State,它標識了線程的當前狀態,取值由名爲 KTHREAD_STATE 的枚舉類型定義:app

typedef enum _KTHREAD_STATE {
    Initialized,
    Ready,
    Running,
    Standby,
    Terminated,
    Waiting,
    Transition,
    DeferredReady,
    GateWait
} KTHREAD_STATE;

已初始化 (Initialized):線程建立過程當中的內部狀態,此時線程不參與調度。
就緒 (Ready):線程已經準備好運行,等待被調度。
運行中 (Running):線程正在某一處理器上運行。
待命 (Standby):線程被選爲某一處理器上下一個將要被執行的線程。
已終止 (Terminated):線程已終止,正在進行資源回收。
等待中 (Waiting):線程正在等待某個條件知足,好比事件對象被觸發。
轉移 (Transition):線程已經準備好運行但內核棧不在內存中。
延遲就緒 (DeferredReady):線程還沒有被肯定在哪一個處理器上運行,此狀態對於單處理器系統沒有意義。
門等待 (GateWait):線程正在等待一個門對象。ide

其中就緒延遲就緒狀態的主要區別是:延遲就緒線程還沒有肯定被分配到哪一個處理器上運行,而就緒線程已經被分配到了某個處理器上。函數


對於線程各個狀態間的轉移規則,能夠參考線程狀態轉移圖(引自潘愛民老師的《Windows內核原理與實現》):學習

圖片描述


二. 進程的當前狀態


進程在內核層所對應的 KPROCESS 結構中,也有一個用來標識當前狀態的 State 成員,它的取值由名爲 KPROCESS_STATE 的枚舉類型定義:spa

typedef enum _KPROCESS_STATE {
    ProcessInMemory,
    ProcessOutOfMemory,
    ProcessInTransition,
    ProcessOutTransition,
    ProcessInSwap,
    ProcessOutSwap
} KPROCESS_STATE;

ProcessInMemory:表示進程的虛擬地址空間內容在物理內存中。
ProcessOutOfMemory:表示進程的虛擬地址空間內容已被換出物理內存。
ProcessInTransition:表示進程的虛擬地址空間內容不在物理內存中,但已請求換入。
ProcessOutTransition:表示進程的虛擬地址空間內容存在於物理內存中,但已請求換出。
ProcessInSwap:表示正在將進程的虛擬地址空間內容換入物理內存,換入完成後,狀態將變動爲 ProcessInMemory
ProcessOutSwap:表示正在將進程的虛擬地址空間內容換出物理內存,換出完成後,狀態將變動爲 ProcessOutOfMemory線程


換入或換出進程的虛擬地址空間會致使進程狀態的切換,此工做是由名爲 平衡集管理器 (Balance Set Manager) 的內核組件負責的,在內核第一階段初始化接近結束時,MmInitSystem 函數建立了兩個平衡集管理器線程,其對應例程分別是 KeBalanceSetManagerKeSwapProcessOrStack 函數。code

KeBalanceSetManager 線程循環等待一個每秒觸發一次的定時器對象和一個工做集管理器事件對象,當等待成功後,它觸發名爲 KiSwapEvent 的事件對象來通知交換線程,以嘗試對知足條件的線程的內核棧執行換出操做。KeSwapProcessOrStack 即爲交換線程,它循環等待上述的 KiSwapEvent 對象,一旦等待成功,會根據狀況執行進程線程內核棧的換入換出工做。對象

一個進程的換出操做發生在進程的 StackCount 爲 0 時,StackCount 記錄了該進程中有多少個線程的內核棧位於內存中,當該進程的全部線程的內核棧都被換出內存時,KiOutSwapKernelStacks 會將進程插入到待換出鏈表中,並觸發 KiSwapEvent 對象,交換線程會在下次循環中調用 KiOutSwapProcesses 函數將該進程換出內存。blog

平衡集管理器實質上是內存管理器組件,有關它更多更詳細的內容將在以後的文章中更新。


三. 調度器主要函數實現


1. KiReadyThread


KiReadyThread 從名字上來看是將一個線程轉爲就緒狀態,而實際上這個函數根據三種不一樣狀況來進行處理:

void __fastcall KiReadyThread(IN PKTHREAD Thread) {

    PKPROCESS Process;

    Process = Thread->ApcState.Process;
    if (Process->State != ProcessInMemory) {

        Thread->State = Ready;
        Thread->ProcessReadyQueue = TRUE;
        InsertTailList(&Process->ReadyListHead, &Thread->WaitListEntry);

        if (Process->State == ProcessOutOfMemory) {
            Process->State = ProcessInTransition;
            InterlockedPushEntrySingleList(&KiProcessInSwapListHead, &Process->SwapListEntry);
            KiSetInternalEvent(&KiSwapEvent, KiSwappingThread);
        }
        return;
        
    } else if (Thread->KernelStackResident == FALSE) {
    
        ASSERT(Process->StackCount != MAXULONG_PTR);
        Process->StackCount += 1;
        
        ASSERT(Thread->State != Transition);
        Thread->State = Transition;
        
        InterlockedPushEntrySingleList(&KiStackInSwapListHead, &Thread->SwapListEntry);
        KiSetInternalEvent(&KiSwapEvent, KiSwappingThread);
        return;
        
    } else {
    
        KiInsertDeferredReadyList(Thread);
        return;
    }
}

分支一:

首先,此函數根據上文提到的 KPROCESSState 成員,來判斷目標線程所屬進程當前是否處於 ProcessInMemory 狀態,即進程虛擬地址空間是否在物理內存中,若不是則將目標線程設置爲就緒狀態,並將線程的 ProcessReadyQueue 標誌設置爲 TRUE,而後將線程插入到所屬進程的就緒鏈表 (ReadyListHead) 中,ProcessReadyQueue 用來標識線程是否在其所屬進程的就緒鏈表中。然後進一步判斷進程是否處於 ProcessOutOfMemory 狀態,如果則將該進程設置爲 ProcessInTransition 狀態,並插入到待換入進程鏈表中,最後觸發 KiSwapEvent 對象通知交換線程執行進程換入操做。由此能夠看出,ProcessInTransition 是一種中間狀態,他標識了進程將要但尚未被執行換入操做,此狀態介於 ProcessInMemoryProcessInSwap 之間。

當進程當前處於 ProcessOutOfMemory 狀態時,其後續操做是:平衡集管理器的交換線程成功等待到 KiSwapEvent,進而調用 KiInSwapProcesses 函數將以前插入到待換入進程鏈表中的進程換入內存(經過 MmInSwapProcess 函數),以後將進程狀態修改成 ProcessInMemory。此時進程虛擬地址空間已在物理內存中,能夠對進程中全部的就緒線程進行調度,因此 KiInSwapProcesses 函數遍歷該進程的就緒鏈表,對其中的全部線程再次調用 KiReadyThread,然後將線程從鏈表中移除。因爲這一次進程已存在於內存中,因此這次 KiReadyThread 函數不會再執行到此分支。

而對於 ProcessInTransitionProcessInSwap 這兩種狀態,則不須要通知交換線程將進程換入內存,由於此時交換線程已經或將要執行 KiInSwapProcesses 函數,如上所述,此函數會在將進程換入內存後,對該進程就緒鏈表中的全部線程再次調用 KiReadyThread。

最後,若進程處於 ProcessOutTransitionProcessOutSwap 狀態(進程因其全部線程的內核棧都被換出內存而致使自身也被換出內存,在換出的過程當中,若是有屬於該進程的新線程被建立,或某一現有線程掛靠到該進程上,則 KiReadyThread 被調用,此時進程可能處於這兩種狀態),那麼剩下的工做將由交換線程經過調用 KiOutSwapProcesses 函數來完成,此函數負責將待換出進程鏈表中的進程換出內存,它在兩個階段分別檢查待換出進程的就緒鏈表:若進程還沒有換出內存,則取消換出操做並將進程狀態修改成 ProcessInMemory,而後對該進程就緒鏈表中的全部線程再次調用 KiReadyThread;若進程已換出內存,則修改進程狀態爲 ProcessInTransition 並觸發 KiSwapEvent 對象,交換線程會在下次循環中調用 KiInSwapProcesses 執行後續操做。

綜上所述,只有當進程處於 ProcessOutOfMemory 狀態時,此函數才通知交換線程將進程換入內存,其他狀況平衡集管理器會進行判斷和處理,而不管哪一種一狀況,進程最後都會變爲 ProcessInMemory 狀態,進而交由其餘分支處理,所謂異途同歸。


分支二:

若是進程當前處於 ProcessInMemory 狀態(經分支一處理後,進程必然處於此狀態),則繼續判斷目標線程的內核棧是否在物理內存中(由 KernelStackResident 標誌指示)。上文提到,線程棧的換入和換出操做也是由平衡集管理器負責的,當一個線程處於等待狀態超過必定時間以後,交換線程調用 KiOutSwapKernelStacks 函數將其內核棧換出物理內存。所以若線程的內核棧已被換出物理內存,則要先通知交換線程將內核棧其換入內存,交換線程經過調用 KiInSwapKernelStacks 函數將線程內核棧換入物理內存,然後直接調用 KiInsertDeferredReadyList 函數將線程插入到延遲就緒鏈表中,關於 KiInsertDeferredReadyList 函數,見分支三

另外上文還提到,進程 KPROCESS 對象中的 StackCount 成員記錄了該進程中有多少個線程的內核棧位於內存中,對於一個將要被換入內存的線程,天然要將其所屬進程的 StackCount 加一(因爲線程終止或掛靠到其餘進程時也會引發 StackCount 的變更,因此此成員不禁平衡集管理器維護)。


分支三:

進入到分支三就表示線程已知足執行條件(內核棧和所屬進程都已在物理內存中),所以調用 KiInsertDeferredReadyList 函數執行下一步操做:

PKPRCB Prcb;
Prcb = KeGetCurrentPrcb();
Thread->State = DeferredReady;
Thread->DeferredProcessor = Prcb->Number;
PushEntryList(&Prcb->DeferredReadyListHead, &Thread->SwapListEntry);

此函數邏輯十分簡單,所作的僅僅是將線程設置爲延遲就緒狀態,並將其插入到當前處理器 PRCB 結構中的延遲就緒鏈表中,之後當調度器得到控制權時,KiProcessDeferredReadyList 函數將遍歷此鏈表,並對每一個線程調用 KiDeferredReadyThread 函數,使其有機會變爲就緒待命狀態。注:此處所說的就緒狀態是真正的就緒,區別於上文所說進程就緒鏈表中的線程,後者不知足執行條件(須要等待其所屬進程被換入內存)。


至此 KiReadyThread 函數已分析完畢,能夠看出,通過此函數處理後的任何線程都會變爲延遲就緒狀態,這對線程來講是一個重要轉折點,意味着它將有機會得到執行權,而在此以前,該線程不會被考慮執行。


TO BE CONTINUED ...

相關文章
相關標籤/搜索