http://www.eeworld.com.cn/mcu/2014/1125/article_17357.html
2014
1 引言
本文分析了Windows 系統的進(jìn)程調度機制,并設計了一種基于Windows 操作系統內核驅動(dòng)的多核CPU 線(xiàn)程管理方法,實(shí)現了一個(gè)基于Windows 內核驅動(dòng)的線(xiàn)程管理服務(wù)系統,它能讓用戶(hù)根據每一個(gè)任務(wù)線(xiàn)程對CPU 資源的需要程度和對實(shí)時(shí)性的要求,在多核CPU上合理為線(xiàn)程分配CPU 核。
Windows 內核調度結構體關(guān)系圖
圖1 Windows 內核調度結構體關(guān)系圖
2 Windows 系統的進(jìn)程調度方法分析
Windows NT 中的每一個(gè)進(jìn)程都是EPROCESS 結構體。此結構體中除了進(jìn)程的屬性之外還引用了其它一些與實(shí)現進(jìn)程緊密相關(guān)的結構體。例如,每個(gè)進(jìn)程都有一個(gè)或幾個(gè)線(xiàn)程,線(xiàn)程在系統中就是ETHREAD 結構體。簡(jiǎn)要描述一下存在于這個(gè)結構體中的主要的信息,這些信息都是由對內核函數的研究而得知的。首先,結構體中有KPROCESS 結構體,這個(gè)結構體中又有指向這些進(jìn)程的內核線(xiàn)程(KTHREAD)鏈表的指針(分配地址空間),基優(yōu)先級,在內核模式或是用戶(hù)模式執行進(jìn)程的線(xiàn)程的時(shí)間,處理器affini ty(掩碼,定義了哪個(gè)處理器能執行進(jìn)程的線(xiàn)程),時(shí)間片值。在ETHREAD 結構體中還存在著(zhù)這樣的信息:進(jìn)程ID、父進(jìn)程ID、進(jìn)程映象名。
在E P R O C E S S 結構體中還有指向P E B 的指針。
ETHREAD 結構體還包含有創(chuàng )建時(shí)間和退出時(shí)間、進(jìn)程ID 和指向EPROCESS 的指針,啟動(dòng)地址,I/O 請求鏈表和KTHREAD 結構體。在KTHREAD 中包含有以下信息:內核模式和用戶(hù)模式線(xiàn)程的創(chuàng )建時(shí)間,指向內核堆?;泛晚旤c(diǎn)的指針、指向服務(wù)表的指針、基優(yōu)先級與當前優(yōu)先級、指向APC 的指針和指向T E B 的指針。
KTHREAD 中包含有許多其它的數據,通過(guò)觀(guān)察這些數據可以分析出KTHREAD 的結構。圖1 描述了這些結構體之間的關(guān)系。
通過(guò)遍歷KPROCESS 結構體中的ETHREAD,找到系統中當前所有的KTHREAD 結構,這個(gè)結構中的偏移量為0x124 處的Affinity 域(Windows XP sp3)即為設置CPU 親緣性掩碼的內存地址。在此重點(diǎn)解釋CPU 親緣性的概念,CPU 親緣性就是指在系統中能夠將一個(gè)或多個(gè)進(jìn)程或線(xiàn)程綁定到一個(gè)或多個(gè)處理器上運行,這是期待已久的特性。也就是說(shuō):" 在1號處理器上一直運行該程序"或者是"在所有的處理器上運行這些程序,而不是在0 號處理器上運行"。然后, 調度器將遵循該規則,程序僅僅運行在允許的處理器上。在Windows 操作系統上,給程序員設定CPU 親緣性的接口是用一個(gè)32 位的雙字型數表示的, 它被稱(chēng)為親緣性掩碼(Affinity bitMask)。親緣性掩碼是一系列的二進(jìn)制位,每一位代表一個(gè)CPU 單元是否可執行當前任務(wù)。例如一個(gè)在具有四個(gè)CPU 的PC 機上( 或四核CPU) ,親緣性掩碼的形式的二進(jìn)制數如下式所示:
0000000000000000000000000000XXXXB
其中自右向左,每一位代表0 到31 號CPU是否可用,由于本機只有四個(gè)CPU, 所以只有前四個(gè)位可用,X 為1則代表當前任務(wù)可執行在此位代表的CPU 上,X 為0 則代表當前任務(wù)不可執行在此位代表的CPU 上, 例如:
00000000000000000000000000000010B
代表當前任務(wù)只能執行在1 號 CPU 上(CPU 下標記數從0 開(kāi)始),又如0x00000004 代表當前任務(wù)只能執行在2 號CPU 上,0x00000003 代表當前任務(wù)可以運行在0號和1 號CPU 上。
Windows 的進(jìn)程調度代碼是在它的SySTem 進(jìn)程下的,所以它不屬于任何用戶(hù)進(jìn)程上下文。調度代碼在適當的時(shí)機會(huì )切換進(jìn)程上下文,這里的切換進(jìn)程上下文是指進(jìn)程環(huán)境的切換, 包括內存中的可執行程序, 提供程序運行的各種資源.進(jìn)程擁有虛擬的地址空間,可執行代碼, 數據, 對象句柄集, 環(huán)境變量, 基礎優(yōu)先級, 以及最大最小工作集等的切換。而Windows 最小的調度單位是線(xiàn)程, 只有線(xiàn)程才是真正的執行體,進(jìn)程只是線(xiàn)程的容器。Windows 的調度程序在時(shí)間片到期,或有切換線(xiàn)程指令執行(如Sleep,KeWaitForSingleObject 等函數)時(shí), 將會(huì )從進(jìn)程線(xiàn)程隊列中找到下一個(gè)要調度的線(xiàn)程執行體,并裝入到KPCR(Kernel ' s Processor CONtr ol Re g i o n , 內核進(jìn)程控制區域) 結構中,CPU 根據KPCR 結構中的KPRCB 結構執行線(xiàn)程執行體代碼。而在多核CPU 下,當Windows 調度代碼執行時(shí),從當前要調度執行的KTHREAD 結構中取出Affinity,并與當前PC 機上的硬件配置數據中的CPU 掩碼作與操作,結果寫(xiě)入到指定的CPU,例如雙核CPU 的設備掩碼為0x03,如果當前KTHREAD 里的Affinity 為0x01,那么0x01&0x03=0x01,這樣執行體線(xiàn)程會(huì )被裝入CPU1的KPRCB 結構中得以執行,調度程序不會(huì )把這個(gè)線(xiàn)程交給CPU2 去執行。此過(guò)程如圖2 所示。這就是為線(xiàn)程選擇指定CPU 核的原理。
Windows 內核親緣性調度原理圖
圖 2 Windows 內核親緣性調度原理圖。
那么控制線(xiàn)程在指定CPU 上運行的突破口就是修改Windows 內核結構體KTHREAD 下的Affinity 域。然而Windows 內核結構被放在虛擬內存線(xiàn)性地址的高2G(不同版本W(wǎng)indows 下也可能是1G)地址空間,用戶(hù)模式下的應用程序是無(wú)法訪(fǎng)問(wèn)這段內存空間的,所以必須編寫(xiě)Windows 驅動(dòng)程序,來(lái)訪(fǎng)問(wèn)Windows 內核內存空間, 這也是本文將要描述的重點(diǎn)。
3 線(xiàn)程管理服務(wù)系統
整個(gè)系統的結構如圖3 所示。該系統由兩大部分組成,分別是內核模式下的管理服務(wù)系統設備驅動(dòng)程序,和用戶(hù)模式下的管理服務(wù)系統應用程序。管理服務(wù)系統應用程序通過(guò)調用Win32 子系統API,向內核下的管理服務(wù)系統驅動(dòng)程序傳遞IRP,內核收到IRP 后,跟據收到的IRP 的內部信息,執行相應的派遣函數,對相應內存進(jìn)行讀寫(xiě),從而給管理服務(wù)系統應用程序提供可用的系統信息。
管理系統總體結構圖
圖3 管理系統總體結構圖。
3.1 內核模式下讀取系統信息
線(xiàn)程管理服務(wù)系統驅動(dòng)程序中,讀取系統信息的方法用到了微軟沒(méi)有公開(kāi)文檔的內核服務(wù)函數,ZwQuerySystemInformATIon,這個(gè)函數被封裝在ntdll.dll模塊中,通過(guò)鏈接ntdll.lib 可得到此函數地址。通過(guò)一個(gè)枚舉量SystemProcessInformaTIon 來(lái)得到進(jìn)程線(xiàn)程相關(guān)信息,填入到第二個(gè)輸入參數SYSTEM_PROCESS_INFORMATION結構中, 這樣就獲得了當前系統關(guān)于進(jìn)程線(xiàn)程的信息。
3.2 內核模式下枚舉系統進(jìn)程線(xiàn)程 SYSTEM_PROCESS_INFORMATION結構中存儲了進(jìn)程及其線(xiàn)程的所有相關(guān)信息,表1 列出了它的具體內容,包括結構內域的地址偏移, 數據類(lèi)型和描述。SYSTEM_PROCESS_INFORMATION的第一個(gè)DWORD型是下一個(gè)進(jìn)程 SYSTEM_PROCESS_INFORMATION相對于當前結構地址的偏移量,可以通過(guò)地址偏移來(lái)遍歷所有的進(jìn)程結構,當遇到某一個(gè)進(jìn)程結構的0 x 0 0 0 0 處的DWORD 型值為0 時(shí),說(shuō)明這個(gè)結構體是系統內最后一個(gè)結構體。線(xiàn)程管理服務(wù)在它的派遣函數中通過(guò)這種方式遍歷所有進(jìn)程,從中提取有用的信息,填入兩個(gè)自定義結構體中。如圖 4 所示,描述了一個(gè)具有兩個(gè)線(xiàn)程的進(jìn)程的數據結構,首先在MY_PROCESS_INFO 結構中填入進(jìn)程的相關(guān)信息,然后根據此進(jìn)程所有的線(xiàn)程數,向系統申請足夠大的分頁(yè)內存空間,PVOID 型指針指向的是第一個(gè)線(xiàn)程結構所在的地址空間,然后向線(xiàn)程結構體中_MY_THREAD_INFO 中填入線(xiàn)程信息,再由線(xiàn)程結構體中的PVOID 型指針指向第二個(gè)線(xiàn)程結構體所在的地址空間,以此類(lèi)推,最后一個(gè)線(xiàn)程結構體的PVOID型指針指向NULL。這樣一個(gè)過(guò)程描述了一個(gè)進(jìn)程及其所屬的所有線(xiàn)程的枚舉過(guò)程,通過(guò)對所有進(jìn)程的遍歷,可以得到系統中的一個(gè)完整的進(jìn)程線(xiàn)程表,存在一段分頁(yè)內存中,這樣在應用程序中便可以得到這些信息。表1 SYSTEM_PROCESS_INFORMATION 結構

SYSTEM_PROCESS_INFORMATION 結構
進(jìn)程線(xiàn)程的兩種數據結構
圖4 進(jìn)程線(xiàn)程的兩種數據結構。
3.3 線(xiàn)程管理服務(wù)系統應用程序設計
進(jìn)程管理服務(wù)系統應用程序是要通過(guò)調用Win 32子系統的API 函數DeviceIoControl 來(lái)向線(xiàn)程管理服務(wù)系統驅動(dòng)程序發(fā)送IRP 的,然后在IRP 結束之后把驅動(dòng)程序中讀出的所有有用進(jìn)程線(xiàn)程信息填入到指定的內存中。這樣線(xiàn)程管理服務(wù)系統應用程序就可以根據所獲得的系統信息句柄來(lái)對線(xiàn)程CPU 親緣性屬性進(jìn)行設置。首先為DeviceIoControl 中的InputBuffer 申請一段內存空間傳入給驅動(dòng)程序,驅動(dòng)程序讀取內核空間進(jìn)程線(xiàn)程信息寫(xiě)入到這段內存中,應用程序讀到信息并顯示給用戶(hù)。
在系統中應用程序為每一個(gè)CPU 維護一個(gè)結構體,內容包括該CPU 是否運行實(shí)時(shí)線(xiàn)程,該CPU 上運行的線(xiàn)程數(如果是實(shí)時(shí)線(xiàn)程CPU線(xiàn)程數為1),以及在此CPU上運行的線(xiàn)程結構數組的首地址。系統通過(guò)對此CPU 結構數組的解析來(lái)對線(xiàn)程進(jìn)行管理。并通過(guò)DeviceIoControl函數把設置后的CPU 結構交給驅動(dòng)程序內核。
3.4 修改Windows 內核結構體
在驅動(dòng)程序讀回應用程序下用戶(hù)的設置結果后,就需要按照用戶(hù)的設定修改KTHREAD 下的Affinity 域的掩碼值了。首先要找到KTHREAD 的線(xiàn)性?xún)却婵臻g,PsGetCurrentProcess()內核函數可以返回內核下當前進(jìn)程空間的E P R O C E S S 結構。E P R O C E S S 結構下的ActiveProcessLinks 域是LIST_ENTRY 結構,通過(guò)它可以遍歷所有的ETHREAD 結構,那么那到KTHREAD 下的Affinity 域就不難了,可以使用兩個(gè)循環(huán)嵌套來(lái)得到所有線(xiàn)程的Affinity 域并將其值設為應用程序中用戶(hù)的設定值。線(xiàn)程CPU 掩碼就被成功的修改了。當CPU 被設定為運行實(shí)時(shí)線(xiàn)程的CPU 時(shí),在它上面運行的線(xiàn)程只能是一個(gè)實(shí)時(shí)線(xiàn)程,這時(shí)的運行線(xiàn)程數被設定為1; 當CPU被設定為非實(shí)時(shí)線(xiàn)程的時(shí)候,上面有可能除了任務(wù)線(xiàn)程運行之外,還有Windows 系統進(jìn)程下的線(xiàn)程。
4 軟件使用及性能測試
4.1 驅動(dòng)的加載及軟件的使用
首先需要把本系統的驅動(dòng)sys 文件加載到Windows的服務(wù)管理器中,加載成功后打開(kāi)應用程序,用戶(hù)可以通過(guò)應用程序中顯示出的當前系統內的進(jìn)程和線(xiàn)程進(jìn)行選擇,并在GUI 圖形界面中對其CPU 占用率及CPU親緣性進(jìn)行設置。
4.2 設置 CPU 親緣性測試
測試運行在雙核CPU 的PC 機上,系統運行一個(gè)要測試的任務(wù)線(xiàn)程(任務(wù)線(xiàn)程為一個(gè)108 次加法運算),四個(gè)其它線(xiàn)程(為測試方便,設為while 循環(huán)線(xiàn)程),限定了循環(huán)線(xiàn)程的CPU 親緣性掩碼為0x0001,任務(wù)線(xiàn)程的CPU親緣性為0x0002,這樣任務(wù)線(xiàn)程與其它線(xiàn)程分別在兩個(gè)核上運行,分別測試了任務(wù)線(xiàn)程單獨運行,任務(wù)線(xiàn)程與其它線(xiàn)程不設定CPU 親緣性,任務(wù)線(xiàn)程與其它線(xiàn)程設定CPU 親緣性三種情況下下任務(wù)線(xiàn)程的運行總時(shí)間如表2 所示。
表2

從表2 分析, 設定任務(wù)線(xiàn)程的CPU 親緣性與其它線(xiàn)程所占用的CPU 分開(kāi),真正意義上的實(shí)現了任務(wù)的異步執行,非常有效的提高了實(shí)時(shí)線(xiàn)程對CPU 資源的使用率。
5 結束語(yǔ)
本文分析了Windows 系統的內核進(jìn)程線(xiàn)程調度表2CPU 親緣性設定三種情況下任務(wù)線(xiàn)程運行時(shí)間表機制,并在此基礎上設計了一種基于Windows 操作系統內核驅動(dòng)的多核CPU 線(xiàn)程管理方法, 實(shí)現了這樣一個(gè)軟件系統。首先在Windows 內核層獲取系統進(jìn)程線(xiàn)程信息,然后再把信息傳入應用層,由應用層上的應用程序根據獲取的信息句柄,對進(jìn)程進(jìn)行操作,用戶(hù)在圖形界面下按照仿真任務(wù)對CPU 資源的不同需求,進(jìn)行相應的設置,可以為指定線(xiàn)程設置CPU 親緣性的功能。在一定程度上為Windows 系統下的任務(wù)合理地分配了CPU 資源,為對實(shí)時(shí)性要求較高的任務(wù)提供了一個(gè)可靠的運行環(huán)境。