Windows線程調度學習(一)

前言

Windows 線程調度器的實現分散在內核各處,并且與許多組件都有關聯,很難進行系統地學習,所以我打算寫幾篇文章來記錄下自己學習過程中的思考和分析,同時也方便日后查閱,此文可以看作是《Windows內核原理與實現》中線程調度部分的讀書筆記和簡單總結。

正文

一. 線程當前狀態


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

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

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

其中就緒延遲就緒狀態的主要區別是:延遲就緒線程尚未確定被分配到哪個處理器上運行,而就緒線程已經被分配到了某個處理器上。


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

圖片描述


二. 進程的當前狀態


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

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 函數。

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

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

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


三. 調度器主要函數實現


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 ...

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/255885.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/255885.shtml
英文地址,請注明出處:http://en.pswp.cn/news/255885.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

機器人的有效負荷

問題: 假如我想在程序里做多幾個有效載荷,但在手動操縱畫面上只能加一個,其它要怎樣用?給個實際例子給我啊. 回答: 在搬運中,確實是有載荷發生變化的情況,如兩抓(A B)的夾具,有三種載荷情況,1、A抓有載荷…

【Python生成readme文件】——Markdown語法

鏈接:https://www.cnblogs.com/wj-1314/p/8547763.html

編程之美2.13子數組的最大乘積

題目: 給定一個長度為N的數組,只許用乘法,不許用除法,計算任意(N-1)個數的組合中乘積最大的一個組,并寫出算法的時間復雜度。 如果把所可能的乘積找出來,共有(N-1&#x…

[SceneKit專題]11-Reference-Nodes引用節點

說明 本系列文章是對<3D Apple Games by Tutorials>一書的學習記錄和體會 此書對應的代碼地址 SceneKit系列文章目錄 本文將完成一個完整的node節點,帶有完整貼圖,并將其導入其他場景中,成為其中的一個引用節點.這樣可以更方便的組織場景,并能復用場景中的節點,正類似于面…

scapy 安裝及簡單測試

關于scapy Scapy的是一個強大的交互式數據包處理程序&#xff08;使用python編寫&#xff09;。它能夠偽造或者解碼大量的網絡協議數據包&#xff0c;能夠發送、捕捉、匹配請求和回復包等等。它可以很容易地處理一些典型操作&#xff0c;比如端口掃描&#xff0c;tracerouting&…

MoveAbsJ在使用時和MOVEJ有什么區別

問 題&#xff1a; MoveAbsJ在使用時和MOVEJ有什么區別 回 答&#xff1a; MoveAbsJ的目標點是用六個軸伺服電機的偏轉角度值來指定的。 MOVEJ和MOVEL的目標點是用坐標系X Y Z的值來指定的。

Python中的序列操作

Python中的序列操作 分類: python undefined 官方手冊&#xff1a;https://docs.python.org/3.7/library/stdtypes.html#sequence-types-list-tuple-range 序列簡介 序列是指按照位置順序來存儲數據的數據結構&#xff0c;也就是說能通過數值索引進行操作。實際上&#x…

automaticallyAdjustsScrollViewInsets的作用

簡單點說就是automaticallyAdjustsScrollViewInsets根據按所在界面的status bar&#xff0c;navigationbar&#xff0c;與tabbar的高度&#xff0c;自動調整scrollview的 inset,設置為no&#xff0c;不讓viewController調整&#xff0c;我們自己修改布局即可~轉載于:https://ww…

JavaScript 基礎知識 - BOM篇

前言 本篇文章是JavaScript基礎知識的BOM篇&#xff0c;如果前面的《JavaScript基礎知識-DOM篇》看完了&#xff0c;現在就可以學習BOM了。 注意&#xff1a; 所有的案例都在這里鏈接: 提取密碼密碼: yvxo&#xff0c;文章中的每個案例后面都有對應的序號。 1. BOM 基本概念 B…

全球首例機器人自殺事件 因受夠無聊家務

據鳳凰網,一個奧地利家庭購買一小機器人,每天工作就是倒垃圾、倒垃圾。一天完工后,它竟自己啟動,爬到爐邊&#xff0c;推開上面的鍋&#xff0c;把自己活活燒死…專家稱這個機器人實在受夠了無聊的家務瑣事&#xff0c;才毅然選擇自殺機器人也是有尊嚴的!為這有骨氣的robot點根…

【python基礎】——數據類型(列表、字典、集合)

駿馬金龍——python語法基礎 python基礎 變量與運算 符號//%**意義整除整除取余冪次方數據種類 #mermaid-svg-7nSRRijcYFCYwTDr .label{font-family:trebuchet ms, verdana, arial;font-family:var(--mermaid-font-family);fill:#333;color:#333}#mermaid-svg-7nSRRijcYFCYw…

linux命令:mkdir命令

命令參數&#xff1a; -m, --mode模式&#xff0c;設定權限<模式> (類似 chmod)&#xff0c;而不是 rwxrwxrwx 減 umask -p, --parents 可以是一個路徑名稱。此時若路徑中的某些目錄尚不存在,加上此選項后,系統將自動建立好那些尚不存在的目錄,即一次可以建立多個目錄; …

js設置奇偶行數樣式

$(document).ready(function () {odd { "background": "none" }; //奇數樣式 even { "background": "#f3f3f3" }; //偶數樣式 odd_even(".gys_xq", odd, even);});function odd_even(id, odd, even) {$(id).find("…

貝塞爾曲線切割圓角

ios 系統框架已經給我們提供了相應的切割圓角的方法, 但是如果在一個見面有很多控件切割的話會出現卡頓和個別不切的現象 ?123456789101112131415161718192021222324252627/* 創建一個Button */UIButton * button [UIButton buttonWithType:(UIButtonTypeSystem)];[button se…

機器人實現屠宰自動化

當 WESTFLEISCH 注冊合作社考慮在 Coesfeld 肉類加工中心內自動化原有的人工屠宰設備過程時&#xff0c;首先在“剔除直腸”及“切開盆腔骨及腹部”兩個流程中測試使用了兩臺庫卡機器人。在此過程中&#xff0c;機器人主要以它工作的質量及經濟效益說服了使用者。 實施措施/解…

DOM編程藝術12章

在submit.html中&#xff0c;代碼簡略成如下也行 <article><h1>Thanks!</h1><p>Thanks for contacting us. Well get back to you as soon as we can.</p></article> </body> </html> 說明了只是插入article的部分&#xff0c…

python數據結構《排序專題復習》

目錄 常見的三種排序方法 冒泡排序 插入排序 選擇排序 其他經典的排序方法 快速排序 堆排序 歸并排序 希爾排序 不同排序方法的各維度對比 排序方式的穩定性&#xff1a;若兩個相同的元素在排序前后的相對位置不發生改變的排序為穩定排序&#xff0c;否則不穩定排序 常…

BZOJ2844 albus就是要第一個出場

AC通道&#xff1a;http://www.lydsy.com/JudgeOnline/problem.php?id2844 這題貌似HDU上有一道差不多的題&#xff0c;不過我沒做過&#xff0c;也就沒管了。 首先講一個線性基的東西&#xff0c;大概就是這樣&#xff1a; 然后就是一個什么性質&#xff1a;S異或起來會出現重…

HTG Explains: Why Linux Doesn’t Need Defragmenting

If you’re a Linux user, you’ve probably heard that you don’t need to defragment your Linux file systems. You’ll also notice that Linux distributions don’t come with disk-defragmenting utilities. But why is that? To understand why Linux file systems d…

Spring AOP 實戰運用

Spring AOP 實戰 看了上面這么多的理論知識, 不知道大家有沒有覺得枯燥哈. 不過不要急, 俗話說理論是實踐的基礎, 對 Spring AOP 有了基本的理論認識后, 我們來看一下下面幾個具體的例子吧.下面的幾個例子是我在工作中所遇見的比較常用的 Spring AOP 的使用場景, 我精簡了很多有…