Windows核心编程 第七章 线程的调度、优先级和亲缘性(下)
7.6 運用結構環境
? ? 現在應該懂得環境結構在線程調度中所起的重要作用了。環境結構使得系統能夠記住線程的狀態,這樣,當下次線程擁有可以運行的C P U時,它就能夠找到它上次中斷運行的地方。
知道這樣低層的數據結構也會完整地記錄在 Platform SDK文檔中確實使人吃驚。不過如果查看該文檔中的C O N T E X T結構,會看到下面這段文字:
? ? “C O N T E X T結構包含了特定處理器的寄存器數據。系統使用 C O N T E X T結構執行各種內部操作。目前,已經存在為 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結構。若要了解這些結構的定義,參見頭文件Wi n N T. h” 。
? ? 該文檔并沒有說明該結構的成員,也沒有描述這些成員是誰,因為這些成員要取決于Windows 2000在哪個C P U上運行。實際上,在Wi n d o w s定義的所有數據結構中,C O N T E X T結構是特定于C P U的唯一數據結構。
? ? 那么C O N T E X T結構中究竟存在哪些東西呢?它包含了主機 C P U上的每個寄存器的數據結構。在x 8 6計算機上,數據成員是E a x、E b x、E c x、E d x等等。如果是A l p h a處理器,那么數據成員包括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結構:
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結構可以分成若干個部分。C O N T E X T _ C O N T R O L包含C P U的控制寄存器,比如指令指針、堆棧指針、標志和函數返回地址(與 x 8 6處理器不同,Alpya CPU在調用函數時,將該函數的返回地址放入一個寄存器中) 。C O N T E X T _ I N T E G E R用于標識C P U的整數寄存器。C O N T E X T _ F L O AT I N G _ P O I N T用于標識C P U的浮點寄存器。C O N T E X T _ S E G M E N T S用于標識C P U的段寄存器(僅為x 8 6處理器) 。CONTEXT_DEBUG_ REGISTER用于標識C P U的調試寄存器(僅為x 8 6處理器) 。CONTEXT_EXTENDED_ REGISTERS用于標識C P U的擴展寄存器(僅為x 8 6處理器) 。
? ? Wi n d o w s實際上允許查看線程內核對象的內部情況,以便抓取它當前的一組 C P U寄存器。若要進行這項操作,只需要調用G e t T h r e a d C o n t e x t函數:
?
? ? 若要調用該函數,只需指定一個C O N T E X T結構,對某些標志(該結構的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。然后該函數將數據填入你要求的成員。
? ? 在調用G e t T h r e a d C o n t e x t函數之前,應該調用S u s p e n d T h r e a d,否則,線程可能被調度,而且線程的環境可能與你收回的不同。一個線程實際上有兩個環境。一個是用戶方式,一個是內核方式。G e t T h r e a d C o n t e x t只能返回線程的用戶方式環境。如果調用S u s p e n d T h r e a d來停止線程的運行,但是該線程目前正在用內核方式運行,那么,即使 S u s p e n d T h r e a d實際上尚未暫停該線程的運行,它的用戶方式仍然處于穩定狀態。線程在恢復用戶方式之前,它無法執行更多的用戶方式代碼,因此可以放心地將線程視為處于暫停狀態, G e t T h r e a d C o n t e x t函數將能正常運行。
? ? C O N T E X T結構的C o n t e x t F l a g s成員并不與任何C P U寄存器相對應。無論是何種 C P U結構,該成員存在于所有C O N T E X T結構定義中。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函數指明你想檢索哪些寄存器。例如,如果想要獲得線程的控制寄存器,可以編寫下面的代碼:
?
? ? 注意,在調用G e t T h r e a d C o n t e x t之前,首先必須對C O N T E X T結構中的C o n t e x t F l a g s成員進
行初始化。
? ? Wi n d o w s為編程人員提供了多么強大的功能啊!如果你認為它確實不錯,那么你一定會喜歡它的,因為Wi n d o w s使你能夠修改C O N T E X T結構中的成員,然后通過調用S e t T h r e a d C o n t e x t將新寄存器值放回線程的內核對象中:
?
? ? 這有可能導致遠程線程中的訪問違規,向用戶顯示未處理的異常消息框,同時,遠程進程終止運行。你將成功地終止另一個進程的運行,而你的進程則可以繼續很好地運行。
? ? 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函數使你能夠對線程進行許多方面的控制,但是在使用它們時應該小心。實際上,幾乎沒有應用程序調用這些函數。增加這些函數是為了增強調試程序和其他工具的功能。任何應用程序都可以調用它們。
7.7 線程的優先級
? ? 本章開頭講述了C P U是如何只使線程運行 2 0 m s,然后調度程序將另一個可調度的線程分配給C P U的。如果所有線程具有相同的優先級,那么就會發生這種情況,但是,在現實環境中,線程被賦予許多不同的優先級,這會影響到調度程序將哪個線程取出來作為下一個要運行的線程。
? ? 每個線程都會被賦予一個從 0(最低)到3 1(最高)的優先級號碼。當系統確定將哪個線程分配給C P U時,它首先觀察優先級為3 1的線程,并以循環方式對它們進行調度。如果優先級為3 1的線程可以調度,那么就將該線程賦予一個C P U。在該線程的時間片結束時,系統要查看是否還有另一個優先級為3 1的線程可以運行,如果有,它將允許該線程被賦予一個C P U。
????只要優先級為3 1的線程是可調度的,系統就絕對不會將優先級為 0到3 0的線程分配給C P U。這種情況稱為渴求調度(s t a r v a t i o n) 。當高優先級線程使用大量的 C P U時間,從而使得低優先級線程無法運行時,便會出現渴求情況。在多處理器計算機上出現渴求情況的可能性要少得多,因為在這樣的計算機上,優先級為3 1和優先級為3 0的線程能夠同時運行。系統總是設法使C P U
保持繁忙狀態,只有當沒有線程可以調度的時候,C P U才處于空閑狀態。
? ? 人們可能認為,在這樣的系統中,低優先級線程永遠得不到機會運行。不過正像前面指出的那樣,在任何一個時段內,系統中的大多數線程是不能調度的。例如,如果進程的主線程調用G e t M e s s a g e函數,而系統發現沒有線程可以供它使用,那么系統就暫停進程的線程運行,釋放該線程的剩余時間片,并且立即將C P U分配給另一個等待運行的線程。
? ? 如果沒有為G e t M e s s a g e函數顯示可供檢索的消息,那么進程的線程將保持暫停狀態,并且決不會被分配給C P U。但是,當消息被置于線程的隊列中時,系統就知道該線程不應該再處于暫停狀態。此時,如果沒有更高優先級的線程需要運行,系統就將該線程分配給一個 C P U。
現在考慮另一個問題。高優先級線程將搶在低優先級線程之前運行,不管低優先級線程正在運行什么。例如,如果一個優先級為 5的線程正在運行,系統發現一個高優先級的線程準備要運行,那么系統就會立即暫停低優先級線程的運行(即使它處于它的時間片中) ,并且將C P U分配給高優先級線程,使它獲得一個完整的時間片。
? ? 還有,當系統引導時,它會創建一個特殊的線程,稱為 0頁線程。該線程被賦予優先級 0,它是整個系統中唯一的一個在優先級0上運行的線程。當系統中沒有任何線程需要執行操作時,0頁線程負責將系統中的所有空閑R A M頁面置0。
7.8 對優先級的抽象說明
???當M i c r o s o f t的開發人員設計線程調度程序時,他們發現該調度程序無法在所有時間適應所有人的需要。他們還發現,計算機的“作用”是不斷變化的。當 Windows NT問世時,對象鏈接和嵌入(O L E)應用程序還剛剛開始編寫。現在,O L E應用程序已經司空見慣。游戲軟件已經相當流行。當然,在Windows NT的早期,并沒有更多地考慮I n t e r n e t的問題。
? ? 調度算法對用戶運行的應用程序類型有著相當大的影響。從一開始, M i c r o s o f t的開發人員就認識到,隨著系統的用途的變化,他們必須不斷修改調度算法。但是,軟件開發人員需要在今天編寫軟件,而M i c r o s o f t則要保證軟件能夠在將來的系統版本上運行。那么 M i c r o s o f t如何改變系統工作的方式并仍然保證軟件能夠運行呢?下面是解決這個問題的一些辦法:
? Microsoft沒有將調度程序的行為特性完全固定下來。
? Microsoft沒有讓應用程序充分利用調度程序的特性。
? Microsoft聲稱調度程序的算法是變化的,在編寫代碼時應有所準備。
? ? Windows API展示了系統的調度程序上的一個抽象層,這樣就永遠不會直接與調度程序進行通信。相反,要調用Wi n d o w s函數,以便根據運行的系統版本“轉換”參數。本章將介紹這個抽象層。
? ? 當設計一個應用程序時, 你應該考慮到還有什么別的應用程序會與你的應用程序一道運行。然后,應該根據你的應用程序中的線程應該具備何種響應性,選擇一個優先級類。這聽起來有些費解,不過情況確實如此。M i c r o s o f t不想作出任何將來可能影響你的代碼運行的承諾。
? ? Wi n d o w s支持6個優先級類:即空閑、低于正常、正常、高于正常、高和實時。當然,正常優先級是最常用的優先級類, 9 9 %的應用程序均使用這個優先級類。表 7 - 4描述了這些優先級類。
?
? ? 當系統什么也不做的時候,將空閑優先級類用于應用程序的運行是最恰當不過的。沒有用交互方式使用的計算機有可能仍然很繁忙(比如作為文件服務器) ,不應該與屏幕保護程序爭用C P U時間。定期更新系統的某些狀態的統計信息跟蹤應用程序不應該干擾關鍵任務的運行。
? ? 只有當絕對必要的時候,才可以使用高優先級類。你會驚奇地發現, Windows Explorer是在高優先級上運行的。大多數時間 E x p l o r e r的線程是暫停的,等待用戶按下操作鍵或者點擊鼠標按鈕時被喚醒。當E x p l o r e r的線程處于暫停狀態時,系統不將它的線程分配給 C P U。因為這將使低優先級線程得以運行。但是一旦用戶按下一個操作鍵或組合鍵,如 C t r l + E s c,系統就會喚醒E x p l o r e r的線程(當用戶按下C t r l + E s c組合鍵時,也會出現S t a r t菜單) 。如果低優先級線程正在運行,系統會立即搶在這些線程的前面,讓E x p l o r e r的線程優先運行。
? ? M i c r o s o f t就是按這種方法設計E x p l o r e r的,因為用戶希望無論系統中正在運行什么,外殼程序都具有極強的響應能力。實際上,即使低優先級線程在無限循環中暫停運行,也能顯示E x p l o r e r的窗口。由于E x p l o r e r的線程擁有較高的優先級,因此執行無限循環的線程被搶占,E x p l o r e r讓用戶終止掛起進程的運行。E x p l o r e r的運行特性非常出色,大部分時間它的線程無事可做,不必占用C P U時間。如果情況不是如此,那么整個系統的運行速度就會慢得多,許多應用程序就不會作出響應。
????應該盡可能避免使用實時優先級類。實際上Windows NT 3.1的早期測試版并沒有向應用程序展示這個優先級類,盡管該操作系統支持這個類。實時優先級是很高的優先級,它可能干擾操作系統任務的運行,因為大多數操作系統線程均以較低的優先級來運行。因此實時線程可能阻止必要的磁盤I / O信息和網絡信息的產生。此外,鍵盤和鼠標輸入將無法及時得到處理,用戶可能以為系統已經暫停運行。大體來說,必須有足夠的理由才能使用實時優先級,比如需要
? ? 以很短的等待時間來響應硬件事件,或者執行某些不能中斷的短期任務。
? ? 注意 除非用戶擁有“提高調度優先級”的權限,否則進程不能用實時優先級類來運行。凡是被指定為管理員或特權用戶的用戶,均默認擁有該權限。
? ? 當然,大多數進程都屬于正常優先級類。低于正常和高于正常的優先級類是 Windows 2000中的新增優先級。M i c r o s o f t增加這些優先級類的原因是,有若干家公司抱怨現有的優先級類無法提供足夠的靈活性。
一旦選定了優先級類之后,就不必考慮你的應用程序與其他應用程序之間的關系,只需要集中考慮你的應用程序中的各個線程。 Wi n d o w s支持7個相對的線程優先級:即空閑、最低、低于正常、正常、高于正常、最高和關鍵時間優先級。這些優先級是相對于進程的優先級類而言的。大多數線程都使用正常線程優先級。表7 - 5描述了這些相對的線程優先級。
?
? ? 概括起來說,進程是優先級類的一個組成部分,你為進程中的線程賦予相對線程優先級。這里沒有講到0到3 1的優先級的任何情況。應用程序開發人員從來不必具體設置優先級。相反,系統負責將進程的優先級類和線程的相對優先級映射到一個優先級上。正是這種映射方式,M i c r o s o f t不想拘泥不變。實際上這種映射方式是隨著系統的版本的升級而變化的。
? ? 表7 - 6顯示了這種映射方式是如何用于Windows 2000的,注意,Windows NT的早期版本和某些Windows 95和Windows 98版本采用了不同的映射方式。未來的Wi n d o w s版本中的映射方式也會變化。
? ? 例如,正常進程中的正常線程被賦予的優先級是 8。由于大多數進程屬于正常優先級類,而大多數線程屬于正常線程優先級,因此系統中的大多數線程的優先級是 8。
? ? 如果高優先級進程中有一個正常線程,該線程的優先級將是 1 3。如果將進程的優先級類改為8,那么線程的優先級就變為4。如果改變了進程的優先級類,線程的相對優先級不變,但是它的優先級的等級卻發生了變化。
?
? ? 注意,表7 - 6并沒有顯示優先級的等級為0的線程。這是因為0優先級保留供零頁線程使用,系統不允許任何其他線程擁有 0優先級。另外,下列優先級等級是無法使用的: 1 7、1 8、1 9、2 0、2 1、2 7、2 8、2 9和3 0。如果編寫一個以內核方式運行的設備驅動程序,可以獲得這些優先級等級,而用戶方式的應用程序則不能。另外還要注意,實時優先級類中的線程不能低于優先級等級1 6。同樣,非實時優先級類中的線程的等級不能高于1 5。
? ? 注意 有些人常常搞不清進程優先級類的概念。他們認為這可能意味著進程是可以調度的。但是進程是根本不能調度的,只有線程才能被調度。進程優先級類是個抽象概念,M i c r o s o f t提出這個概念的目的,是為了幫助你將它與調度程序的內部運行情況區分開來。它沒有其他目的。
? ? 注意 一般來說,大多數時候高優先級的線程不應該處于可調度狀態。當線程要進行某種操作時,它能迅速獲得C P U時間。這時線程應該盡可能少地執行 C P U指令,并返回睡眠狀態,等待再次變成可調度狀態。相反,低優先級的線程可以保持可調度狀態,執行大量的C P U指令來進行它的操作。如果按照這些原則來辦,整個操作系統就能正確地對用戶作出響應。
7.9 程序的優先級
????進程是如何被賦予優先級類的呢?當調用C r e a t e P r o c e s s時,可以在f d w C r e a t e參數中傳遞需要的優先級類。表7 - 7顯示了優先級類的標識符。
?
? ? 創建子進程的進程負責選擇子進程運行的優先級類,這看起來有點奇怪。讓我們以E x p l o r e r為例來說明這個問題。當使用E x p l o r e r來運行一個應用程序時,新進程按正常優先級運行。E x p l o r e r不知道進程在做什么,也不知道隔多長時間它的線程需要進行調度。但是,一旦子進程運行,它就能夠通過調用S e t P r i o r i t y C l a s s來改變它自己的優先級類:
?
? ? 該函數將h P r o c e s s標識的優先級類改為f d w P r i o r i t y參數中設定的值。f d w P r i o r i t y參數可以是表7 - 7顯示的標識符之一。由于該函數帶有一個進程句柄,因此,只要擁有該進程的句柄和足夠的訪問權,就能夠改變系統中運行的任何進程的優先級類。
一般來說,進程將試圖改變它自己的優先級類。下面是如何使一個進程將它自己的優先級類設置為空閑的例子:
? ? SetPriorityClass(GetCurrentProcess() ,HIGH_PRIORITY_CLASS);
? ? 下面是用來檢索進程的優先級類的補充函數:
? ? DWORD GetPriorityClass(HANDLE hProcess);
? ? 正如你所期望的那樣,該函數將返回表7 - 7中列出的標識符之一。
? ? 當使用命令外殼啟動一個程序時,該程序的起始優先級是正常優先級。但是,如果使用S t a r t命令來啟動該程序,可以使用一個開關來設定應用程序的起始優先級。例如,在命令外殼輸入下面的命令可使系統啟動C a l c u l a t o r,并在開始時按空閑優先級來運行它:
?
? ? 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等開關,以便按它們各自的優先級啟動執行一個應用程序。當然,一旦應用程序啟動運行,它就可以調用S e t P r i o r i t y C l a s s函數,將它自己的優先級改為它選擇的任何優先級。
? ? Windows 98 Windows 98的S t a r t命令并不支持這些開關中的任何一個。Windows 98命令外殼啟動的進程總是使用正常優先級類來運行。
? ? Windows 2000的Task Manager使得用戶可以改變進程的優先級類。圖 7 - 2顯示了Ta s kM a n a g e r的P r o c e s s e s選項卡,它顯示了當前運行的所有進程。Base Pri列顯示了每個進程的優先級類。可以改變進程的優先級類,方法是選定一個進程,然后從上下文菜單的 Set Priority(設置優先級)子菜單中選擇一個選項。
?
? ? 當一個線程剛剛創建時,它的相對線程優先級總是設置為正常優先級。我總感到有些奇怪,C r e a t e T h r e a d沒有為調用者提供一個設置新線程的相對優先級的方法。若要設置和獲得線程的相對優先級,必須調用下面的這些函數:
?
? ? 當然,h T h r e a d參數用于標識想要改變優先級的單個線程, n P r i o r i t y參數是表7 - 8列出的7個標識符之一。
?
下面是檢索線程的相對優先級的補充函數:
int GetThreadPriority(HANDLE hThread);
該函數返回表7 - 8列出的標識符之一。
若要創建一個帶有相對優先級為空閑的線程,可以執行類似下面的代碼:
?
? ? 注意,C r e a t e T h r e a d函數創建的新函數帶有的相對優先級總是正常優先級。若要使線程以空閑優先級來運行,應該將C R E AT E _ S U S P E N D E D標志傳遞給C r e a t e T h r e a d函數,這可以防止線程執行任何代碼。然后可以調用 S e t T h r e a d P r i o r i t y,將線程的優先級改為相對空閑優先級。這時可以調用R e s u m e T h r e a d,使得線程成為可調度的線程。你不知道線程何時能夠獲得 C P U時間,但是調度程序會考慮這樣一個情況,即該線程擁有一個空閑優先級。最后,可以關閉新線程的句柄,一旦線程終止運行,內核對象就能被撤消。
? ? 注意 Wi n d o w s沒有提供返回線程的優先級的函數。這是故意進行的。記住,M i c r o s o f t保留了隨時修改調度算法的權利。你不會設計需要調度算法專門知識的應用程序。如果堅持使用進程優先級類和相對線程優先級,你的應用程序不僅現在能夠順利地運行,而且在系統的將來版本上也能很好地運行。
?7.9.1 動態提高線程的優先級等級
? ? 通過將線程的相對優先級與線程的進程優先級類綜合起來考慮,系統就可以確定線程的優級等級。有時這稱為線程的 基本優先級等級。系統常常要提高線程的優先級等級,以便對窗口消息或讀取磁盤等I / O事件作出響應。
? ? 例如,在高優先級類進程中的一個正常優先級等級的線程的基本優先級等級是 1 3。如果用戶按下一個操作鍵,系統就會將一個 W M _ K E Y D O W N消息放入線程的隊列中。由于一個消息已經出現在線程的隊列中,因此該線程就是可調度的線程。此外,鍵盤設備驅動程序也能夠告訴系統暫時提高線程的優先級等級。該線程的優先級等級可能提高 2級,其當前優先級等級改為1 5。
? ? 系統在優先級為1 5時為一個時間片對該線程進行調度。一旦該時間片結束,系統便將線程的優先級遞減1,使下一個時間片的線程優先級降為 1 4。該線程的第三個時間片按優先級等級1 3來執行。如果線程要求執行更多的時間片,均按它的基本優先級等級 1 3來執行。
? ? 注意,線程的當前優先級等級決不會低于線程的基本優先級等級。此外,導致線程成為可調度線程的設備驅動程序可以決定優先級等級提高的數量。 M i c r o s o f t并沒有規定各個設備驅動程序可以給線程的優先級提高多少個等級。這樣就使得 M i c r o s o f t可以不斷地調整線程優先級提高的動態等級,以確定最佳的總體響應性能。
? ? 系統只能為基本優先級等級在 1至1 5之間的線程提高其優先級等級。實際上這是因為這個范圍稱為動態優先級范圍。此外,系統決不會將線程的優先級等級提高到實時范圍(高于 1 5) 。由于實時范圍中的線程能夠執行大多數操作系統的函數,因此給等級的提高規定一個范圍,就可以防止應用程序干擾操作系統的運行。另外,系統決不會動態提高實時范圍內的線程優先級等級。
? ? 有些編程人員抱怨說,系統動態提高線程優先級等級的功能對他們的線程性能會產生一種不良的影響,為此M i c r o s o f t增加了下面兩個函數,這樣就能夠使系統的動態提高線程優先級等級的功能不起作用:
?
? ? S e t P r o c e s s P r i o r i t y B o o s t負責告訴系統激活或停用進行中的所有線程的優先級提高功能,而S e t T h r e a d P r i o r i t y B o o s t則讓你激活或停用各個線程的優先級提高功能。這兩個函數具有許多相似的共性,可以用來確定是激活還是停用優先級提高功能:
?
? ? 對于這兩個函數中的每個函數,可以傳遞想要查詢的進程或線程的句柄,以及由函數設置的B O O L的地址。
? ? ? ?Windows 98 Windows 98沒有提供這4個函數的有用的實現代碼。它們全部返回FA L S E,后來對G e t L a s t E r r o r的調用將返回E R R O R _ C A L L _ N O T _ I M P L E M E N T E D。
? ? 另一種情況也會導致系統動態地提高線程的優先級等級。比如有一個優先級為 4的線程準備運行但是卻不能運行,因為一個優先級為8的線程正連續被調度。在這種情況下,優先級為4的線程就非常渴望得到C P U時間。當系統發現一個線程在大約3至4 s內一直渴望得到C P U時間,它就將這個渴望得到C P U時間的線程的優先級動態提高到1 5,并讓該線程運行兩倍于它的時間量。當到了兩倍時間量的時候,該線程的優先級立即返回到它的基本優先級。
7.9.2 為前臺進程調整調度程序
? ?當用戶對進程的窗口進行操作時,該進程就稱為前臺進程,所有其他進程則稱為后臺進程。當然,用戶希望他正在使用的進程比后臺進程具有更強的響應性。為了提高前臺進程的響應性,Wi n d o w s能夠為前臺進程中的線程調整其調度算法。對于Windows 2000來說,系統可以為前臺進程的線程提供比通常多的C P U時間量。這種調整只能在前臺進程屬于正常優先級類的進程時才能進行。如果它屬于其他任何優先級類,就無法進行任何調整。
? ? Windows 2000實際上允許用戶對這種調整進行相應的配置。在 System Properties(系統屬性)對話框的A d v a n c e d選項卡上,用戶可以單擊Performance Options(性能選項)按鈕,打開圖7 - 3所示的對話框
?
? ? 如果用戶選擇優化應用程序的性能,系統就執行配置的調整。如果用戶選擇優化后臺服務程序的性能,系統就不進行調整。當安裝 Windows 2000的專業版時,A p p l i c a t i o n s就會被默認選定。對于Windows 2000的所有其他版本,則默認選定Background Services,因為計算機將主要由非交互式用戶使用。
? ? 當進程移到前臺時,Windows 98也會對正常優先級類的進程中的線程調度算法進行調整。當一個優先級為正常的進程移到前臺時,系統便將最低、低于正常、正常、高于正常和最高等優先級的線程的優先級提高 1,優先級為空閑和關鍵時間的線程的優先級則不予提高。因此,在正常優先級類的進程中運行的、其相對優先級為正常的線程,它的優先級等級是 9而不是8。當進程返回后臺時,進程中的線程便自動返回它們定義好的基本優先級等級。
? ? Windows 98 Windows 98沒有提供允許用戶配置這種調整手段的任何用戶界面,因為Windows 98不是作為專用服務器來運行的。
? ?將進程改為前臺進程的原因是,使它們能夠對用戶的輸入更快地作出響應。如果不改為前臺進程,那么在后臺的正常打印進程與在后臺接收用戶輸入的正常進程就會平等地爭用 C P U時間。用戶會發現文本無法在前臺應用程序中順利地顯示。但是,由于系統改變了前臺進程的線程優先級,前臺進程的線程就能對用戶的輸入更好地作出響應。
7.10 親緣性
? ? 按照默認設置,當系統將線程分配給處理器時, Windows 2000使用軟親緣性來進行操作。這意味著如果所有其他因素相同的話,它將設法在它上次運行的那個處理器上運行線程。讓線程留在單個處理器上,有助于重復使用仍然在處理器的內存高速緩存中的數據。
? ? 有一種新的計算機結構,稱為 N U M A(非統一內存訪問) ,在該結構中,計算機包含若干塊插件板,每個插件板上有 4個C P U和它自己的內存區。圖7 - 6顯示了一臺配有3塊插件板的計算機,總共有1 2個C P U,這樣,任何一個線程都可以在1 2個C P U中的任何一個上運行。
?
? ? 當C P U訪問的內存是它自己的插件板上的內存時, N U M A系統運行的性能最好。如果C P U需要訪問位于另一個插件板上的內存時,性能就會大大降低。在這樣的環境中,就需要來自一個進程中的線程在CPU 0至3上運行,讓另一個進程中的線程在 CPU 4至7上運行,依次類推。為了適應這種計算機結構的需要,Windows 2000允許設置進程和線程的親緣性。換句話說,可以控制哪個C P U能夠運行某些線程。這稱為硬親緣性。
? ? 計算機在引導時,系統要確定機器中有多少個C P U可供使用。通過調用G e t S y s t e m I n f o函數(第1 4章介紹) ,應用程序就能查詢機器中的C P U數量。按照默認設置,任何線程都可以調度到這些C P U中的任何一個上去運行。 為了限制在可用C P U的子集上運行的單個進程中的線程數量,可以調用S e t P r o c e s s A ff i n i t y M a s k:
?
? ? 第一個參數h P r o c e s s用于指明要影響的是哪個進程。第二個參數 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。此外,可以使用作業內核對象將一組進程限制在要求的一組C P U上運行。
? ? 當然,還有一個函數也能夠返回進程的親緣性位屏蔽,它就是 G e t P r o c e s s A ff i n i t y M a s k,如面的代碼所示:
?
? ? 這里也可以傳遞想要親緣性屏蔽的進程句柄,該函數填入 p d w P r o c e s s A ff i n i t y M a s k指向的變量。該函數還能返回系統的親緣性屏蔽(在 p d w S y s t e m A ff i n i t y M a s k指向的變量中) 。系統的親緣性屏蔽用于指明系統的哪個C P U能夠處理線程。進程的親緣性屏蔽始終是一個系統的親緣性屏蔽的正確子集。
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填入兩個變量中。
? ? 到現在為止,已經介紹了如何將進程的多個線程限制到一組 C P U上去運行。有時可能想要將進程中的一個線程限制到一組 C P U上去運行。例如,可能有一個包含 4個線程的進程,它們在擁有4個C P U的計算機上運行。如果這些線程中的一個線程正在執行非常重要的操作,而你想增加某個C P U始終可供它使用的可能性,為此你對其他 3個線程進行了限制,使它們不能在CPU 0上運行,而只能在CPU 1、2和3上運行。
? ? 通過調用S e t T h r e a d A ff i n i t y M a s k,就能為各個線程設置親緣性屏蔽:
?
? ? 該函數中的h T h r e a d參數用于指明要限制哪個線程,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必須是進程的親緣性屏蔽的相應子集。返回值是線程的前一個親緣性屏蔽。因此,若要將 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參數必須始終是1。
? ? 當一個x 8 6系統引導時,系統要執行相應的代碼,以便測定主機上的哪些 C P U遇到了著名的P e n t i u m浮點錯誤。系統必須為每個 C P U測試其浮點錯誤,方法是將線程的親緣性設置為第一個C P U,執行潛在的故障分割操作,并將結果與已知的正確答案進行比較。然后對下一個C P U進行上述同樣的操作,如此等等。
? ? 注意 在大多數環境中,改變線程的親緣性就會影響調度程序有效地在各個 C P U之間移植線程的能力,而這種能力可以最有效地使用C P U時間。表7 - 9顯示了一個例子。
?
? ? 當線程A被喚醒時,調度程序發現該線程可以在 CPU 0上運行,因此它被分配給CPU 0。然后線程B被喚醒,調度程序發現該線程可以被分配給 CPU 0或1,但是,由于CPU 0正在使用之中,因此調度程序將線程B分配給了CPU 1。至此,一切進行得都很順利。
? ? 這時線程C被喚醒,調度程序發現它只能在CPU 1上運行。但是CPU 1正在被線程B使用著,它是個優先級為8的線程。由于線程C的優先級為6,因此它不能搶在線程B的前面運行。線程C可以搶在線程A的前面運行,因為線程A的優先級是4,但是調度程序不會使它搶在線程A的前面運行,因為線程C不能在CPU 0上運行。
? ? 這顯示出為線程設置硬親緣性將會對調度程序的優先級設置方案產生什么樣的影響。
有時強制將一個線程分配給特定的 C P U的做法是不妥當的。例如,有 3個線程都只能在CPU 0上運行,而CPU 1、2和3則閑著無事可做。在這種情況下,如果告訴系統想讓一個線程在某個C P U上運行,但是允許該線程在可能的情況下移到另一個 C P U上去運行,那么這種辦法會更好些。
? ? 若要為線程設置一個理想的C P U,可以調用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用于指明要為哪個線程設置首選的 C P U。與我們已經介紹的其他函數不同,d w I d e a l P r o c e s s o r函數不是個位屏蔽函數,它是個從0到3 1的整數,用于指明供線程使用的首選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。如果沒有為該線程設置理想的 C P U,那么該函數返回前一個理想的 C P U或M A X I M U M _ P R O C E S S O R S。
?
總結
以上是生活随笔為你收集整理的Windows核心编程 第七章 线程的调度、优先级和亲缘性(下)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Windows核心编程 第七章 线程的调
- 下一篇: Windows Pe 第三章 PE头文件