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