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