Windows核心编程 第七章 线程的调度、优先级和亲缘性(下)
7.6 運用結(jié)構(gòu)環(huán)境
? ? 現(xiàn)在應(yīng)該懂得環(huán)境結(jié)構(gòu)在線程調(diào)度中所起的重要作用了。環(huán)境結(jié)構(gòu)使得系統(tǒng)能夠記住線程的狀態(tài),這樣,當(dāng)下次線程擁有可以運行的C P U時,它就能夠找到它上次中斷運行的地方。
知道這樣低層的數(shù)據(jù)結(jié)構(gòu)也會完整地記錄在 Platform SDK文檔中確實使人吃驚。不過如果查看該文檔中的C O N T E X T結(jié)構(gòu),會看到下面這段文字:
? ? “C O N T E X T結(jié)構(gòu)包含了特定處理器的寄存器數(shù)據(jù)。系統(tǒng)使用 C O N T E X T結(jié)構(gòu)執(zhí)行各種內(nèi)部操作。目前,已經(jīng)存在為 I n t e l、M I P S、A l p h a和P o w e r P C處理器定義的C O N T E X T結(jié)構(gòu)。若要了解這些結(jié)構(gòu)的定義,參見頭文件Wi n N T. h” 。
? ? 該文檔并沒有說明該結(jié)構(gòu)的成員,也沒有描述這些成員是誰,因為這些成員要取決于Windows 2000在哪個C P U上運行。實際上,在Wi n d o w s定義的所有數(shù)據(jù)結(jié)構(gòu)中,C O N T E X T結(jié)構(gòu)是特定于C P U的唯一數(shù)據(jù)結(jié)構(gòu)。
? ? 那么C O N T E X T結(jié)構(gòu)中究竟存在哪些東西呢?它包含了主機 C P U上的每個寄存器的數(shù)據(jù)結(jié)構(gòu)。在x 8 6計算機上,數(shù)據(jù)成員是E a x、E b x、E c x、E d x等等。如果是A l p h a處理器,那么數(shù)據(jù)成員包括I n t V 0、I n t T 0、I n t T 1、I n t S 0、I n t R a和I n t Z e r o等等。下面這個代碼段顯示了 x86 CPU的完整的C O N T E X T結(jié)構(gòu):
typedef struct _CONTEXT {
?
????//
????// The flags values within this flag control the contents of
????// a CONTEXT record.
????//
????// If the context record is used as an input parameter, then
????// for each portion of the context record controlled by a flag
????// whose value is set, it is assumed that that portion of the
????// context record contains valid context. If the context record
????// is being used to modify a threads context, then only that
????// portion of the threads context will be modified.
????//
????// If the context record is used as an IN OUT parameter to capture
????// the context of a thread, then only those portions of the thread's
????// context corresponding to set flags will be returned.
????//
????// The context record is never used as an OUT only parameter.
????//
?
????DWORD ContextFlags;
?
????//
????// This section is specified/returned if CONTEXT_DEBUG_REGISTERS is
????// set in ContextFlags. ?Note that CONTEXT_DEBUG_REGISTERS is NOT
????// included in CONTEXT_FULL.
????//
?
????DWORD ??Dr0;
????DWORD ??Dr1;
????DWORD ??Dr2;
????DWORD ??Dr3;
????DWORD ??Dr6;
????DWORD ??Dr7;
?
????//
????// This section is specified/returned if the
????// ContextFlags word contians the flag CONTEXT_FLOATING_POINT.
????//
?
????FLOATING_SAVE_AREA FloatSave;
?
????//
????// This section is specified/returned if the
????// ContextFlags word contians the flag CONTEXT_SEGMENTS.
????//
?
????DWORD ??SegGs;
????DWORD ??SegFs;
????DWORD ??SegEs;
????DWORD ??SegDs;
?
????//
????// This section is specified/returned if the
????// ContextFlags word contians the flag CONTEXT_INTEGER.
????//
?
????DWORD ??Edi;
????DWORD ??Esi;
????DWORD ??Ebx;
????DWORD ??Edx;
????DWORD ??Ecx;
????DWORD ??Eax;
?
????//
????// This section is specified/returned if the
????// ContextFlags word contians the flag CONTEXT_CONTROL.
????//
?
????DWORD ??Ebp;
????DWORD ??Eip;
????DWORD ??SegCs; ?????????????// MUST BE SANITIZED
????DWORD ??EFlags; ????????????// MUST BE SANITIZED
????DWORD ??Esp;
????DWORD ??SegSs;
?
????//
????// This section is specified/returned if the ContextFlags word
????// contains the flag CONTEXT_EXTENDED_REGISTERS.
????// The format and contexts are processor specific
????//
?
????BYTE ???ExtendedRegisters[MAXIMUM_SUPPORTED_EXTENSION];
?
} CONTEXT;
?
? ? typedef CONTEXT *PCONTEXT;
? ? C O N T E X T結(jié)構(gòu)可以分成若干個部分。C O N T E X T _ C O N T R O L包含C P U的控制寄存器,比如指令指針、堆棧指針、標(biāo)志和函數(shù)返回地址(與 x 8 6處理器不同,Alpya CPU在調(diào)用函數(shù)時,將該函數(shù)的返回地址放入一個寄存器中) 。C O N T E X T _ I N T E G E R用于標(biāo)識C P U的整數(shù)寄存器。C O N T E X T _ F L O AT I N G _ P O I N T用于標(biāo)識C P U的浮點寄存器。C O N T E X T _ S E G M E N T S用于標(biāo)識C P U的段寄存器(僅為x 8 6處理器) 。CONTEXT_DEBUG_ REGISTER用于標(biāo)識C P U的調(diào)試寄存器(僅為x 8 6處理器) 。CONTEXT_EXTENDED_ REGISTERS用于標(biāo)識C P U的擴展寄存器(僅為x 8 6處理器) 。
? ? Wi n d o w s實際上允許查看線程內(nèi)核對象的內(nèi)部情況,以便抓取它當(dāng)前的一組 C P U寄存器。若要進行這項操作,只需要調(diào)用G e t T h r e a d C o n t e x t函數(shù):
?
? ? 若要調(diào)用該函數(shù),只需指定一個C O N T E X T結(jié)構(gòu),對某些標(biāo)志(該結(jié)構(gòu)的C o n t e x t F l a g s成員)進行初始化,指明想要收回哪些寄存器,并將該結(jié)構(gòu)的地址傳遞給 G e t T h r e a d C o n t e x t。然后該函數(shù)將數(shù)據(jù)填入你要求的成員。
? ? 在調(diào)用G e t T h r e a d C o n t e x t函數(shù)之前,應(yīng)該調(diào)用S u s p e n d T h r e a d,否則,線程可能被調(diào)度,而且線程的環(huán)境可能與你收回的不同。一個線程實際上有兩個環(huán)境。一個是用戶方式,一個是內(nèi)核方式。G e t T h r e a d C o n t e x t只能返回線程的用戶方式環(huán)境。如果調(diào)用S u s p e n d T h r e a d來停止線程的運行,但是該線程目前正在用內(nèi)核方式運行,那么,即使 S u s p e n d T h r e a d實際上尚未暫停該線程的運行,它的用戶方式仍然處于穩(wěn)定狀態(tài)。線程在恢復(fù)用戶方式之前,它無法執(zhí)行更多的用戶方式代碼,因此可以放心地將線程視為處于暫停狀態(tài), G e t T h r e a d C o n t e x t函數(shù)將能正常運行。
? ? C O N T E X T結(jié)構(gòu)的C o n t e x t F l a g s成員并不與任何C P U寄存器相對應(yīng)。無論是何種 C P U結(jié)構(gòu),該成員存在于所有C O N T E X T結(jié)構(gòu)定義中。C o n t e x t F l a g s成員用于向G e t T h r e a d C o n t e x t函數(shù)指明你想檢索哪些寄存器。例如,如果想要獲得線程的控制寄存器,可以編寫下面的代碼:
?
? ? 注意,在調(diào)用G e t T h r e a d C o n t e x t之前,首先必須對C O N T E X T結(jié)構(gòu)中的C o n t e x t F l a g s成員進
行初始化。
? ? Wi n d o w s為編程人員提供了多么強大的功能啊!如果你認(rèn)為它確實不錯,那么你一定會喜歡它的,因為Wi n d o w s使你能夠修改C O N T E X T結(jié)構(gòu)中的成員,然后通過調(diào)用S e t T h r e a d C o n t e x t將新寄存器值放回線程的內(nèi)核對象中:
?
? ? 這有可能導(dǎo)致遠(yuǎn)程線程中的訪問違規(guī),向用戶顯示未處理的異常消息框,同時,遠(yuǎn)程進程終止運行。你將成功地終止另一個進程的運行,而你的進程則可以繼續(xù)很好地運行。
? ? G e t T h r e a d C o n t e x t和S e t T h r e a d C o n t e x t函數(shù)使你能夠?qū)€程進行許多方面的控制,但是在使用它們時應(yīng)該小心。實際上,幾乎沒有應(yīng)用程序調(diào)用這些函數(shù)。增加這些函數(shù)是為了增強調(diào)試程序和其他工具的功能。任何應(yīng)用程序都可以調(diào)用它們。
7.7 線程的優(yōu)先級
? ? 本章開頭講述了C P U是如何只使線程運行 2 0 m s,然后調(diào)度程序?qū)⒘硪粋€可調(diào)度的線程分配給C P U的。如果所有線程具有相同的優(yōu)先級,那么就會發(fā)生這種情況,但是,在現(xiàn)實環(huán)境中,線程被賦予許多不同的優(yōu)先級,這會影響到調(diào)度程序?qū)⒛膫€線程取出來作為下一個要運行的線程。
? ? 每個線程都會被賦予一個從 0(最低)到3 1(最高)的優(yōu)先級號碼。當(dāng)系統(tǒng)確定將哪個線程分配給C P U時,它首先觀察優(yōu)先級為3 1的線程,并以循環(huán)方式對它們進行調(diào)度。如果優(yōu)先級為3 1的線程可以調(diào)度,那么就將該線程賦予一個C P U。在該線程的時間片結(jié)束時,系統(tǒng)要查看是否還有另一個優(yōu)先級為3 1的線程可以運行,如果有,它將允許該線程被賦予一個C P U。
????只要優(yōu)先級為3 1的線程是可調(diào)度的,系統(tǒng)就絕對不會將優(yōu)先級為 0到3 0的線程分配給C P U。這種情況稱為渴求調(diào)度(s t a r v a t i o n) 。當(dāng)高優(yōu)先級線程使用大量的 C P U時間,從而使得低優(yōu)先級線程無法運行時,便會出現(xiàn)渴求情況。在多處理器計算機上出現(xiàn)渴求情況的可能性要少得多,因為在這樣的計算機上,優(yōu)先級為3 1和優(yōu)先級為3 0的線程能夠同時運行。系統(tǒng)總是設(shè)法使C P U
保持繁忙狀態(tài),只有當(dāng)沒有線程可以調(diào)度的時候,C P U才處于空閑狀態(tài)。
? ? 人們可能認(rèn)為,在這樣的系統(tǒng)中,低優(yōu)先級線程永遠(yuǎn)得不到機會運行。不過正像前面指出的那樣,在任何一個時段內(nèi),系統(tǒng)中的大多數(shù)線程是不能調(diào)度的。例如,如果進程的主線程調(diào)用G e t M e s s a g e函數(shù),而系統(tǒng)發(fā)現(xiàn)沒有線程可以供它使用,那么系統(tǒng)就暫停進程的線程運行,釋放該線程的剩余時間片,并且立即將C P U分配給另一個等待運行的線程。
? ? 如果沒有為G e t M e s s a g e函數(shù)顯示可供檢索的消息,那么進程的線程將保持暫停狀態(tài),并且決不會被分配給C P U。但是,當(dāng)消息被置于線程的隊列中時,系統(tǒng)就知道該線程不應(yīng)該再處于暫停狀態(tài)。此時,如果沒有更高優(yōu)先級的線程需要運行,系統(tǒng)就將該線程分配給一個 C P U。
現(xiàn)在考慮另一個問題。高優(yōu)先級線程將搶在低優(yōu)先級線程之前運行,不管低優(yōu)先級線程正在運行什么。例如,如果一個優(yōu)先級為 5的線程正在運行,系統(tǒng)發(fā)現(xiàn)一個高優(yōu)先級的線程準(zhǔn)備要運行,那么系統(tǒng)就會立即暫停低優(yōu)先級線程的運行(即使它處于它的時間片中) ,并且將C P U分配給高優(yōu)先級線程,使它獲得一個完整的時間片。
? ? 還有,當(dāng)系統(tǒng)引導(dǎo)時,它會創(chuàng)建一個特殊的線程,稱為 0頁線程。該線程被賦予優(yōu)先級 0,它是整個系統(tǒng)中唯一的一個在優(yōu)先級0上運行的線程。當(dāng)系統(tǒng)中沒有任何線程需要執(zhí)行操作時,0頁線程負(fù)責(zé)將系統(tǒng)中的所有空閑R A M頁面置0。
7.8 對優(yōu)先級的抽象說明
???當(dāng)M i c r o s o f t的開發(fā)人員設(shè)計線程調(diào)度程序時,他們發(fā)現(xiàn)該調(diào)度程序無法在所有時間適應(yīng)所有人的需要。他們還發(fā)現(xiàn),計算機的“作用”是不斷變化的。當(dāng) Windows NT問世時,對象鏈接和嵌入(O L E)應(yīng)用程序還剛剛開始編寫。現(xiàn)在,O L E應(yīng)用程序已經(jīng)司空見慣。游戲軟件已經(jīng)相當(dāng)流行。當(dāng)然,在Windows NT的早期,并沒有更多地考慮I n t e r n e t的問題。
? ? 調(diào)度算法對用戶運行的應(yīng)用程序類型有著相當(dāng)大的影響。從一開始, M i c r o s o f t的開發(fā)人員就認(rèn)識到,隨著系統(tǒng)的用途的變化,他們必須不斷修改調(diào)度算法。但是,軟件開發(fā)人員需要在今天編寫軟件,而M i c r o s o f t則要保證軟件能夠在將來的系統(tǒng)版本上運行。那么 M i c r o s o f t如何改變系統(tǒng)工作的方式并仍然保證軟件能夠運行呢?下面是解決這個問題的一些辦法:
? Microsoft沒有將調(diào)度程序的行為特性完全固定下來。
? Microsoft沒有讓應(yīng)用程序充分利用調(diào)度程序的特性。
? Microsoft聲稱調(diào)度程序的算法是變化的,在編寫代碼時應(yīng)有所準(zhǔn)備。
? ? Windows API展示了系統(tǒng)的調(diào)度程序上的一個抽象層,這樣就永遠(yuǎn)不會直接與調(diào)度程序進行通信。相反,要調(diào)用Wi n d o w s函數(shù),以便根據(jù)運行的系統(tǒng)版本“轉(zhuǎn)換”參數(shù)。本章將介紹這個抽象層。
? ? 當(dāng)設(shè)計一個應(yīng)用程序時, 你應(yīng)該考慮到還有什么別的應(yīng)用程序會與你的應(yīng)用程序一道運行。然后,應(yīng)該根據(jù)你的應(yīng)用程序中的線程應(yīng)該具備何種響應(yīng)性,選擇一個優(yōu)先級類。這聽起來有些費解,不過情況確實如此。M i c r o s o f t不想作出任何將來可能影響你的代碼運行的承諾。
? ? Wi n d o w s支持6個優(yōu)先級類:即空閑、低于正常、正常、高于正常、高和實時。當(dāng)然,正常優(yōu)先級是最常用的優(yōu)先級類, 9 9 %的應(yīng)用程序均使用這個優(yōu)先級類。表 7 - 4描述了這些優(yōu)先級類。
?
? ? 當(dāng)系統(tǒng)什么也不做的時候,將空閑優(yōu)先級類用于應(yīng)用程序的運行是最恰當(dāng)不過的。沒有用交互方式使用的計算機有可能仍然很繁忙(比如作為文件服務(wù)器) ,不應(yīng)該與屏幕保護程序爭用C P U時間。定期更新系統(tǒng)的某些狀態(tài)的統(tǒng)計信息跟蹤應(yīng)用程序不應(yīng)該干擾關(guān)鍵任務(wù)的運行。
? ? 只有當(dāng)絕對必要的時候,才可以使用高優(yōu)先級類。你會驚奇地發(fā)現(xiàn), Windows Explorer是在高優(yōu)先級上運行的。大多數(shù)時間 E x p l o r e r的線程是暫停的,等待用戶按下操作鍵或者點擊鼠標(biāo)按鈕時被喚醒。當(dāng)E x p l o r e r的線程處于暫停狀態(tài)時,系統(tǒng)不將它的線程分配給 C P U。因為這將使低優(yōu)先級線程得以運行。但是一旦用戶按下一個操作鍵或組合鍵,如 C t r l + E s c,系統(tǒng)就會喚醒E x p l o r e r的線程(當(dāng)用戶按下C t r l + E s c組合鍵時,也會出現(xiàn)S t a r t菜單) 。如果低優(yōu)先級線程正在運行,系統(tǒng)會立即搶在這些線程的前面,讓E x p l o r e r的線程優(yōu)先運行。
? ? M i c r o s o f t就是按這種方法設(shè)計E x p l o r e r的,因為用戶希望無論系統(tǒng)中正在運行什么,外殼程序都具有極強的響應(yīng)能力。實際上,即使低優(yōu)先級線程在無限循環(huán)中暫停運行,也能顯示E x p l o r e r的窗口。由于E x p l o r e r的線程擁有較高的優(yōu)先級,因此執(zhí)行無限循環(huán)的線程被搶占,E x p l o r e r讓用戶終止掛起進程的運行。E x p l o r e r的運行特性非常出色,大部分時間它的線程無事可做,不必占用C P U時間。如果情況不是如此,那么整個系統(tǒng)的運行速度就會慢得多,許多應(yīng)用程序就不會作出響應(yīng)。
????應(yīng)該盡可能避免使用實時優(yōu)先級類。實際上Windows NT 3.1的早期測試版并沒有向應(yīng)用程序展示這個優(yōu)先級類,盡管該操作系統(tǒng)支持這個類。實時優(yōu)先級是很高的優(yōu)先級,它可能干擾操作系統(tǒng)任務(wù)的運行,因為大多數(shù)操作系統(tǒng)線程均以較低的優(yōu)先級來運行。因此實時線程可能阻止必要的磁盤I / O信息和網(wǎng)絡(luò)信息的產(chǎn)生。此外,鍵盤和鼠標(biāo)輸入將無法及時得到處理,用戶可能以為系統(tǒng)已經(jīng)暫停運行。大體來說,必須有足夠的理由才能使用實時優(yōu)先級,比如需要
? ? 以很短的等待時間來響應(yīng)硬件事件,或者執(zhí)行某些不能中斷的短期任務(wù)。
? ? 注意 除非用戶擁有“提高調(diào)度優(yōu)先級”的權(quán)限,否則進程不能用實時優(yōu)先級類來運行。凡是被指定為管理員或特權(quán)用戶的用戶,均默認(rèn)擁有該權(quán)限。
? ? 當(dāng)然,大多數(shù)進程都屬于正常優(yōu)先級類。低于正常和高于正常的優(yōu)先級類是 Windows 2000中的新增優(yōu)先級。M i c r o s o f t增加這些優(yōu)先級類的原因是,有若干家公司抱怨現(xiàn)有的優(yōu)先級類無法提供足夠的靈活性。
一旦選定了優(yōu)先級類之后,就不必考慮你的應(yīng)用程序與其他應(yīng)用程序之間的關(guān)系,只需要集中考慮你的應(yīng)用程序中的各個線程。 Wi n d o w s支持7個相對的線程優(yōu)先級:即空閑、最低、低于正常、正常、高于正常、最高和關(guān)鍵時間優(yōu)先級。這些優(yōu)先級是相對于進程的優(yōu)先級類而言的。大多數(shù)線程都使用正常線程優(yōu)先級。表7 - 5描述了這些相對的線程優(yōu)先級。
?
? ? 概括起來說,進程是優(yōu)先級類的一個組成部分,你為進程中的線程賦予相對線程優(yōu)先級。這里沒有講到0到3 1的優(yōu)先級的任何情況。應(yīng)用程序開發(fā)人員從來不必具體設(shè)置優(yōu)先級。相反,系統(tǒng)負(fù)責(zé)將進程的優(yōu)先級類和線程的相對優(yōu)先級映射到一個優(yōu)先級上。正是這種映射方式,M i c r o s o f t不想拘泥不變。實際上這種映射方式是隨著系統(tǒng)的版本的升級而變化的。
? ? 表7 - 6顯示了這種映射方式是如何用于Windows 2000的,注意,Windows NT的早期版本和某些Windows 95和Windows 98版本采用了不同的映射方式。未來的Wi n d o w s版本中的映射方式也會變化。
? ? 例如,正常進程中的正常線程被賦予的優(yōu)先級是 8。由于大多數(shù)進程屬于正常優(yōu)先級類,而大多數(shù)線程屬于正常線程優(yōu)先級,因此系統(tǒng)中的大多數(shù)線程的優(yōu)先級是 8。
? ? 如果高優(yōu)先級進程中有一個正常線程,該線程的優(yōu)先級將是 1 3。如果將進程的優(yōu)先級類改為8,那么線程的優(yōu)先級就變?yōu)?/span>4。如果改變了進程的優(yōu)先級類,線程的相對優(yōu)先級不變,但是它的優(yōu)先級的等級卻發(fā)生了變化。
?
? ? 注意,表7 - 6并沒有顯示優(yōu)先級的等級為0的線程。這是因為0優(yōu)先級保留供零頁線程使用,系統(tǒng)不允許任何其他線程擁有 0優(yōu)先級。另外,下列優(yōu)先級等級是無法使用的: 1 7、1 8、1 9、2 0、2 1、2 7、2 8、2 9和3 0。如果編寫一個以內(nèi)核方式運行的設(shè)備驅(qū)動程序,可以獲得這些優(yōu)先級等級,而用戶方式的應(yīng)用程序則不能。另外還要注意,實時優(yōu)先級類中的線程不能低于優(yōu)先級等級1 6。同樣,非實時優(yōu)先級類中的線程的等級不能高于1 5。
? ? 注意 有些人常常搞不清進程優(yōu)先級類的概念。他們認(rèn)為這可能意味著進程是可以調(diào)度的。但是進程是根本不能調(diào)度的,只有線程才能被調(diào)度。進程優(yōu)先級類是個抽象概念,M i c r o s o f t提出這個概念的目的,是為了幫助你將它與調(diào)度程序的內(nèi)部運行情況區(qū)分開來。它沒有其他目的。
? ? 注意 一般來說,大多數(shù)時候高優(yōu)先級的線程不應(yīng)該處于可調(diào)度狀態(tài)。當(dāng)線程要進行某種操作時,它能迅速獲得C P U時間。這時線程應(yīng)該盡可能少地執(zhí)行 C P U指令,并返回睡眠狀態(tài),等待再次變成可調(diào)度狀態(tài)。相反,低優(yōu)先級的線程可以保持可調(diào)度狀態(tài),執(zhí)行大量的C P U指令來進行它的操作。如果按照這些原則來辦,整個操作系統(tǒng)就能正確地對用戶作出響應(yīng)。
7.9 程序的優(yōu)先級
????進程是如何被賦予優(yōu)先級類的呢?當(dāng)調(diào)用C r e a t e P r o c e s s時,可以在f d w C r e a t e參數(shù)中傳遞需要的優(yōu)先級類。表7 - 7顯示了優(yōu)先級類的標(biāo)識符。
?
? ? 創(chuàng)建子進程的進程負(fù)責(zé)選擇子進程運行的優(yōu)先級類,這看起來有點奇怪。讓我們以E x p l o r e r為例來說明這個問題。當(dāng)使用E x p l o r e r來運行一個應(yīng)用程序時,新進程按正常優(yōu)先級運行。E x p l o r e r不知道進程在做什么,也不知道隔多長時間它的線程需要進行調(diào)度。但是,一旦子進程運行,它就能夠通過調(diào)用S e t P r i o r i t y C l a s s來改變它自己的優(yōu)先級類:
?
? ? 該函數(shù)將h P r o c e s s標(biāo)識的優(yōu)先級類改為f d w P r i o r i t y參數(shù)中設(shè)定的值。f d w P r i o r i t y參數(shù)可以是表7 - 7顯示的標(biāo)識符之一。由于該函數(shù)帶有一個進程句柄,因此,只要擁有該進程的句柄和足夠的訪問權(quán),就能夠改變系統(tǒng)中運行的任何進程的優(yōu)先級類。
一般來說,進程將試圖改變它自己的優(yōu)先級類。下面是如何使一個進程將它自己的優(yōu)先級類設(shè)置為空閑的例子:
? ? SetPriorityClass(GetCurrentProcess() ,HIGH_PRIORITY_CLASS);
? ? 下面是用來檢索進程的優(yōu)先級類的補充函數(shù):
? ? DWORD GetPriorityClass(HANDLE hProcess);
? ? 正如你所期望的那樣,該函數(shù)將返回表7 - 7中列出的標(biāo)識符之一。
? ? 當(dāng)使用命令外殼啟動一個程序時,該程序的起始優(yōu)先級是正常優(yōu)先級。但是,如果使用S t a r t命令來啟動該程序,可以使用一個開關(guān)來設(shè)定應(yīng)用程序的起始優(yōu)先級。例如,在命令外殼輸入下面的命令可使系統(tǒng)啟動C a l c u l a t o r,并在開始時按空閑優(yōu)先級來運行它:
?
? ? S t a r t命令還能識別 / B E L O W N O R M A L、/ N O R M A L、/ A B O V E N O R M A L、/ H I G H和/ R E A LT I M E等開關(guān),以便按它們各自的優(yōu)先級啟動執(zhí)行一個應(yīng)用程序。當(dāng)然,一旦應(yīng)用程序啟動運行,它就可以調(diào)用S e t P r i o r i t y C l a s s函數(shù),將它自己的優(yōu)先級改為它選擇的任何優(yōu)先級。
? ? Windows 98 Windows 98的S t a r t命令并不支持這些開關(guān)中的任何一個。Windows 98命令外殼啟動的進程總是使用正常優(yōu)先級類來運行。
? ? Windows 2000的Task Manager使得用戶可以改變進程的優(yōu)先級類。圖 7 - 2顯示了Ta s kM a n a g e r的P r o c e s s e s選項卡,它顯示了當(dāng)前運行的所有進程。Base Pri列顯示了每個進程的優(yōu)先級類。可以改變進程的優(yōu)先級類,方法是選定一個進程,然后從上下文菜單的 Set Priority(設(shè)置優(yōu)先級)子菜單中選擇一個選項。
?
? ? 當(dāng)一個線程剛剛創(chuàng)建時,它的相對線程優(yōu)先級總是設(shè)置為正常優(yōu)先級。我總感到有些奇怪,C r e a t e T h r e a d沒有為調(diào)用者提供一個設(shè)置新線程的相對優(yōu)先級的方法。若要設(shè)置和獲得線程的相對優(yōu)先級,必須調(diào)用下面的這些函數(shù):
?
? ? 當(dāng)然,h T h r e a d參數(shù)用于標(biāo)識想要改變優(yōu)先級的單個線程, n P r i o r i t y參數(shù)是表7 - 8列出的7個標(biāo)識符之一。
?
下面是檢索線程的相對優(yōu)先級的補充函數(shù):
int GetThreadPriority(HANDLE hThread);
該函數(shù)返回表7 - 8列出的標(biāo)識符之一。
若要創(chuàng)建一個帶有相對優(yōu)先級為空閑的線程,可以執(zhí)行類似下面的代碼:
?
? ? 注意,C r e a t e T h r e a d函數(shù)創(chuàng)建的新函數(shù)帶有的相對優(yōu)先級總是正常優(yōu)先級。若要使線程以空閑優(yōu)先級來運行,應(yīng)該將C R E AT E _ S U S P E N D E D標(biāo)志傳遞給C r e a t e T h r e a d函數(shù),這可以防止線程執(zhí)行任何代碼。然后可以調(diào)用 S e t T h r e a d P r i o r i t y,將線程的優(yōu)先級改為相對空閑優(yōu)先級。這時可以調(diào)用R e s u m e T h r e a d,使得線程成為可調(diào)度的線程。你不知道線程何時能夠獲得 C P U時間,但是調(diào)度程序會考慮這樣一個情況,即該線程擁有一個空閑優(yōu)先級。最后,可以關(guān)閉新線程的句柄,一旦線程終止運行,內(nèi)核對象就能被撤消。
? ? 注意 Wi n d o w s沒有提供返回線程的優(yōu)先級的函數(shù)。這是故意進行的。記住,M i c r o s o f t保留了隨時修改調(diào)度算法的權(quán)利。你不會設(shè)計需要調(diào)度算法專門知識的應(yīng)用程序。如果堅持使用進程優(yōu)先級類和相對線程優(yōu)先級,你的應(yīng)用程序不僅現(xiàn)在能夠順利地運行,而且在系統(tǒng)的將來版本上也能很好地運行。
?7.9.1 動態(tài)提高線程的優(yōu)先級等級
? ? 通過將線程的相對優(yōu)先級與線程的進程優(yōu)先級類綜合起來考慮,系統(tǒng)就可以確定線程的優(yōu)級等級。有時這稱為線程的 基本優(yōu)先級等級。系統(tǒng)常常要提高線程的優(yōu)先級等級,以便對窗口消息或讀取磁盤等I / O事件作出響應(yīng)。
? ? 例如,在高優(yōu)先級類進程中的一個正常優(yōu)先級等級的線程的基本優(yōu)先級等級是 1 3。如果用戶按下一個操作鍵,系統(tǒng)就會將一個 W M _ K E Y D O W N消息放入線程的隊列中。由于一個消息已經(jīng)出現(xiàn)在線程的隊列中,因此該線程就是可調(diào)度的線程。此外,鍵盤設(shè)備驅(qū)動程序也能夠告訴系統(tǒng)暫時提高線程的優(yōu)先級等級。該線程的優(yōu)先級等級可能提高 2級,其當(dāng)前優(yōu)先級等級改為1 5。
? ? 系統(tǒng)在優(yōu)先級為1 5時為一個時間片對該線程進行調(diào)度。一旦該時間片結(jié)束,系統(tǒng)便將線程的優(yōu)先級遞減1,使下一個時間片的線程優(yōu)先級降為 1 4。該線程的第三個時間片按優(yōu)先級等級1 3來執(zhí)行。如果線程要求執(zhí)行更多的時間片,均按它的基本優(yōu)先級等級 1 3來執(zhí)行。
? ? 注意,線程的當(dāng)前優(yōu)先級等級決不會低于線程的基本優(yōu)先級等級。此外,導(dǎo)致線程成為可調(diào)度線程的設(shè)備驅(qū)動程序可以決定優(yōu)先級等級提高的數(shù)量。 M i c r o s o f t并沒有規(guī)定各個設(shè)備驅(qū)動程序可以給線程的優(yōu)先級提高多少個等級。這樣就使得 M i c r o s o f t可以不斷地調(diào)整線程優(yōu)先級提高的動態(tài)等級,以確定最佳的總體響應(yīng)性能。
? ? 系統(tǒng)只能為基本優(yōu)先級等級在 1至1 5之間的線程提高其優(yōu)先級等級。實際上這是因為這個范圍稱為動態(tài)優(yōu)先級范圍。此外,系統(tǒng)決不會將線程的優(yōu)先級等級提高到實時范圍(高于 1 5) 。由于實時范圍中的線程能夠執(zhí)行大多數(shù)操作系統(tǒng)的函數(shù),因此給等級的提高規(guī)定一個范圍,就可以防止應(yīng)用程序干擾操作系統(tǒng)的運行。另外,系統(tǒng)決不會動態(tài)提高實時范圍內(nèi)的線程優(yōu)先級等級。
? ? 有些編程人員抱怨說,系統(tǒng)動態(tài)提高線程優(yōu)先級等級的功能對他們的線程性能會產(chǎn)生一種不良的影響,為此M i c r o s o f t增加了下面兩個函數(shù),這樣就能夠使系統(tǒng)的動態(tài)提高線程優(yōu)先級等級的功能不起作用:
?
? ? S e t P r o c e s s P r i o r i t y B o o s t負(fù)責(zé)告訴系統(tǒng)激活或停用進行中的所有線程的優(yōu)先級提高功能,而S e t T h r e a d P r i o r i t y B o o s t則讓你激活或停用各個線程的優(yōu)先級提高功能。這兩個函數(shù)具有許多相似的共性,可以用來確定是激活還是停用優(yōu)先級提高功能:
?
? ? 對于這兩個函數(shù)中的每個函數(shù),可以傳遞想要查詢的進程或線程的句柄,以及由函數(shù)設(shè)置的B O O L的地址。
? ? ? ?Windows 98 Windows 98沒有提供這4個函數(shù)的有用的實現(xiàn)代碼。它們?nèi)糠祷?/span>FA L S E,后來對G e t L a s t E r r o r的調(diào)用將返回E R R O R _ C A L L _ N O T _ I M P L E M E N T E D。
? ? 另一種情況也會導(dǎo)致系統(tǒng)動態(tài)地提高線程的優(yōu)先級等級。比如有一個優(yōu)先級為 4的線程準(zhǔn)備運行但是卻不能運行,因為一個優(yōu)先級為8的線程正連續(xù)被調(diào)度。在這種情況下,優(yōu)先級為4的線程就非常渴望得到C P U時間。當(dāng)系統(tǒng)發(fā)現(xiàn)一個線程在大約3至4 s內(nèi)一直渴望得到C P U時間,它就將這個渴望得到C P U時間的線程的優(yōu)先級動態(tài)提高到1 5,并讓該線程運行兩倍于它的時間量。當(dāng)?shù)搅藘杀稌r間量的時候,該線程的優(yōu)先級立即返回到它的基本優(yōu)先級。
7.9.2 為前臺進程調(diào)整調(diào)度程序
? ?當(dāng)用戶對進程的窗口進行操作時,該進程就稱為前臺進程,所有其他進程則稱為后臺進程。當(dāng)然,用戶希望他正在使用的進程比后臺進程具有更強的響應(yīng)性。為了提高前臺進程的響應(yīng)性,Wi n d o w s能夠為前臺進程中的線程調(diào)整其調(diào)度算法。對于Windows 2000來說,系統(tǒng)可以為前臺進程的線程提供比通常多的C P U時間量。這種調(diào)整只能在前臺進程屬于正常優(yōu)先級類的進程時才能進行。如果它屬于其他任何優(yōu)先級類,就無法進行任何調(diào)整。
? ? Windows 2000實際上允許用戶對這種調(diào)整進行相應(yīng)的配置。在 System Properties(系統(tǒng)屬性)對話框的A d v a n c e d選項卡上,用戶可以單擊Performance Options(性能選項)按鈕,打開圖7 - 3所示的對話框
?
? ? 如果用戶選擇優(yōu)化應(yīng)用程序的性能,系統(tǒng)就執(zhí)行配置的調(diào)整。如果用戶選擇優(yōu)化后臺服務(wù)程序的性能,系統(tǒng)就不進行調(diào)整。當(dāng)安裝 Windows 2000的專業(yè)版時,A p p l i c a t i o n s就會被默認(rèn)選定。對于Windows 2000的所有其他版本,則默認(rèn)選定Background Services,因為計算機將主要由非交互式用戶使用。
? ? 當(dāng)進程移到前臺時,Windows 98也會對正常優(yōu)先級類的進程中的線程調(diào)度算法進行調(diào)整。當(dāng)一個優(yōu)先級為正常的進程移到前臺時,系統(tǒng)便將最低、低于正常、正常、高于正常和最高等優(yōu)先級的線程的優(yōu)先級提高 1,優(yōu)先級為空閑和關(guān)鍵時間的線程的優(yōu)先級則不予提高。因此,在正常優(yōu)先級類的進程中運行的、其相對優(yōu)先級為正常的線程,它的優(yōu)先級等級是 9而不是8。當(dāng)進程返回后臺時,進程中的線程便自動返回它們定義好的基本優(yōu)先級等級。
? ? Windows 98 Windows 98沒有提供允許用戶配置這種調(diào)整手段的任何用戶界面,因為Windows 98不是作為專用服務(wù)器來運行的。
? ?將進程改為前臺進程的原因是,使它們能夠?qū)τ脩舻妮斎敫斓刈鞒鲰憫?yīng)。如果不改為前臺進程,那么在后臺的正常打印進程與在后臺接收用戶輸入的正常進程就會平等地爭用 C P U時間。用戶會發(fā)現(xiàn)文本無法在前臺應(yīng)用程序中順利地顯示。但是,由于系統(tǒng)改變了前臺進程的線程優(yōu)先級,前臺進程的線程就能對用戶的輸入更好地作出響應(yīng)。
7.10 親緣性
? ? 按照默認(rèn)設(shè)置,當(dāng)系統(tǒng)將線程分配給處理器時, Windows 2000使用軟親緣性來進行操作。這意味著如果所有其他因素相同的話,它將設(shè)法在它上次運行的那個處理器上運行線程。讓線程留在單個處理器上,有助于重復(fù)使用仍然在處理器的內(nèi)存高速緩存中的數(shù)據(jù)。
? ? 有一種新的計算機結(jié)構(gòu),稱為 N U M A(非統(tǒng)一內(nèi)存訪問) ,在該結(jié)構(gòu)中,計算機包含若干塊插件板,每個插件板上有 4個C P U和它自己的內(nèi)存區(qū)。圖7 - 6顯示了一臺配有3塊插件板的計算機,總共有1 2個C P U,這樣,任何一個線程都可以在1 2個C P U中的任何一個上運行。
?
? ? 當(dāng)C P U訪問的內(nèi)存是它自己的插件板上的內(nèi)存時, N U M A系統(tǒng)運行的性能最好。如果C P U需要訪問位于另一個插件板上的內(nèi)存時,性能就會大大降低。在這樣的環(huán)境中,就需要來自一個進程中的線程在CPU 0至3上運行,讓另一個進程中的線程在 CPU 4至7上運行,依次類推。為了適應(yīng)這種計算機結(jié)構(gòu)的需要,Windows 2000允許設(shè)置進程和線程的親緣性。換句話說,可以控制哪個C P U能夠運行某些線程。這稱為硬親緣性。
? ? 計算機在引導(dǎo)時,系統(tǒng)要確定機器中有多少個C P U可供使用。通過調(diào)用G e t S y s t e m I n f o函數(shù)(第1 4章介紹) ,應(yīng)用程序就能查詢機器中的C P U數(shù)量。按照默認(rèn)設(shè)置,任何線程都可以調(diào)度到這些C P U中的任何一個上去運行。 為了限制在可用C P U的子集上運行的單個進程中的線程數(shù)量,可以調(diào)用S e t P r o c e s s A ff i n i t y M a s k:
?
? ? 第一個參數(shù)h P r o c e s s用于指明要影響的是哪個進程。第二個參數(shù) d w P r o c e s s A ff i n i t y M a s k是個位屏蔽,用于指明線程可以在哪些C P U上運行。例如,傳遞0 x 0 0 0 0 0 0 0 5表示該進程中的線程可以在CPU 0和CPU 2上運行,但是不能在CPU 1和C P U 3至3 1上運行。
? ? 注意,子進程可以繼承進程的親緣性。因此,如果一個進程的親緣性屏蔽是 0 x 0 0 0 0 0 0 0 5,那么它的子進程中的任何線程都擁有相同的位屏蔽,并共享相同的 C P U。此外,可以使用作業(yè)內(nèi)核對象將一組進程限制在要求的一組C P U上運行。
? ? 當(dāng)然,還有一個函數(shù)也能夠返回進程的親緣性位屏蔽,它就是 G e t P r o c e s s A ff i n i t y M a s k,如面的代碼所示:
?
? ? 這里也可以傳遞想要親緣性屏蔽的進程句柄,該函數(shù)填入 p d w P r o c e s s A ff i n i t y M a s k指向的變量。該函數(shù)還能返回系統(tǒng)的親緣性屏蔽(在 p d w S y s t e m A ff i n i t y M a s k指向的變量中) 。系統(tǒng)的親緣性屏蔽用于指明系統(tǒng)的哪個C P U能夠處理線程。進程的親緣性屏蔽始終是一個系統(tǒng)的親緣性屏蔽的正確子集。
Windows 98 無論計算機中實際擁有多少個C P U,Windows 98只使用一個C P U。因此,G e t P r o c e s s A ff i n i t y M a s k總是用1填入兩個變量中。
? ? 到現(xiàn)在為止,已經(jīng)介紹了如何將進程的多個線程限制到一組 C P U上去運行。有時可能想要將進程中的一個線程限制到一組 C P U上去運行。例如,可能有一個包含 4個線程的進程,它們在擁有4個C P U的計算機上運行。如果這些線程中的一個線程正在執(zhí)行非常重要的操作,而你想增加某個C P U始終可供它使用的可能性,為此你對其他 3個線程進行了限制,使它們不能在CPU 0上運行,而只能在CPU 1、2和3上運行。
? ? 通過調(diào)用S e t T h r e a d A ff i n i t y M a s k,就能為各個線程設(shè)置親緣性屏蔽:
?
? ? 該函數(shù)中的h T h r e a d參數(shù)用于指明要限制哪個線程,d w T h r e a d A ff i n i t y M a s k用于指明該線程能夠在哪個C P U上運行。d w T h r e a d A ff i n i t y M a s k必須是進程的親緣性屏蔽的相應(yīng)子集。返回值是線程的前一個親緣性屏蔽。因此,若要將 3個線程限制到CPU 1、2和3上去運行,可以這樣操作:
?
? ? Windows 98 由于計算機中無論配有多少個C P U,Windows 98只使用一個C P U,因此d w T h r e a d A ff i n i t y M a s k參數(shù)必須始終是1。
? ? 當(dāng)一個x 8 6系統(tǒng)引導(dǎo)時,系統(tǒng)要執(zhí)行相應(yīng)的代碼,以便測定主機上的哪些 C P U遇到了著名的P e n t i u m浮點錯誤。系統(tǒng)必須為每個 C P U測試其浮點錯誤,方法是將線程的親緣性設(shè)置為第一個C P U,執(zhí)行潛在的故障分割操作,并將結(jié)果與已知的正確答案進行比較。然后對下一個C P U進行上述同樣的操作,如此等等。
? ? 注意 在大多數(shù)環(huán)境中,改變線程的親緣性就會影響調(diào)度程序有效地在各個 C P U之間移植線程的能力,而這種能力可以最有效地使用C P U時間。表7 - 9顯示了一個例子。
?
? ? 當(dāng)線程A被喚醒時,調(diào)度程序發(fā)現(xiàn)該線程可以在 CPU 0上運行,因此它被分配給CPU 0。然后線程B被喚醒,調(diào)度程序發(fā)現(xiàn)該線程可以被分配給 CPU 0或1,但是,由于CPU 0正在使用之中,因此調(diào)度程序?qū)⒕€程B分配給了CPU 1。至此,一切進行得都很順利。
? ? 這時線程C被喚醒,調(diào)度程序發(fā)現(xiàn)它只能在CPU 1上運行。但是CPU 1正在被線程B使用著,它是個優(yōu)先級為8的線程。由于線程C的優(yōu)先級為6,因此它不能搶在線程B的前面運行。線程C可以搶在線程A的前面運行,因為線程A的優(yōu)先級是4,但是調(diào)度程序不會使它搶在線程A的前面運行,因為線程C不能在CPU 0上運行。
? ? 這顯示出為線程設(shè)置硬親緣性將會對調(diào)度程序的優(yōu)先級設(shè)置方案產(chǎn)生什么樣的影響。
有時強制將一個線程分配給特定的 C P U的做法是不妥當(dāng)?shù)摹@?#xff0c;有 3個線程都只能在CPU 0上運行,而CPU 1、2和3則閑著無事可做。在這種情況下,如果告訴系統(tǒng)想讓一個線程在某個C P U上運行,但是允許該線程在可能的情況下移到另一個 C P U上去運行,那么這種辦法會更好些。
? ? 若要為線程設(shè)置一個理想的C P U,可以調(diào)用S e t T h r e a d I d e a l P r o c e s s o r :
?
? ?h T h r e a d用于指明要為哪個線程設(shè)置首選的 C P U。與我們已經(jīng)介紹的其他函數(shù)不同,d w I d e a l P r o c e s s o r函數(shù)不是個位屏蔽函數(shù),它是個從0到3 1的整數(shù),用于指明供線程使用的首選C P U。可以傳遞一個M A X I M U M _ P R O C E S S O R S的值(在Wi n N T. h中定義為3 2) ,用于指明不存在理想的C P U。如果沒有為該線程設(shè)置理想的 C P U,那么該函數(shù)返回前一個理想的 C P U或M A X I M U M _ P R O C E S S O R S。
?
總結(jié)
以上是生活随笔為你收集整理的Windows核心编程 第七章 线程的调度、优先级和亲缘性(下)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Windows核心编程 第七章 线程的调
- 下一篇: Windows Pe 第三章 PE头文件