Windows核心编程 第六章 线程基础知识 (下)
6.6 線程的一些性質
? ? 到現在為止,講述了如何實現線程函數和如何讓系統創建線程以便執行該函數。本節將要介紹系統如何使這些操作獲得成功。
? ? 圖6 - 1顯示了系統在創建線程和對線程進行初始化時必須做些什么工作。讓我們仔細看一看這個圖,以便確切地了解發生的具體情況。調用 C r e a t e T h r e a d可使系統創建一個線程內核對象。該對象的初始使用計數是2(在線程停止運行和從C r e a t e T h r e a d返回的句柄關閉之前,線程內核對象不會被撤消) 。線程的內核對象的其他屬性也被初始化,暫停計數被設置為 1,退出代
碼始終為S T I L L _ A C T I V E(0 x 1 0 3) ,該對象設置為未通知狀態。
?
? ? 一旦內核對象創建完成,系統就分配用于線程的堆棧的內存。該內存是從進程的地址空間分配而來的,因為線程并不擁有它自己的地址空間。然后系統將兩個值寫入新線程的堆棧的上端(線程堆棧總是從內存的高地址向低地址建立) 。寫入堆棧的第一個值是傳遞給C r e a t e T h r e a d的p v P a r a m參數的值。緊靠它的下面是傳遞給C r e a t e T h r e a d的p f n S t a r t A d d r參數的值。
? ? 每個線程都有它自己的一組C P U寄存器,稱為線程的上下文。該上下文反映了線程上次運行時該線程的 C P U寄存器的狀態。線程的這組 C P U寄存器保存在一個 C O N T E X T結構(在Wi n N T. h頭文件中作了定義)中。C O N T E X T結構本身則包含在線程的內核對象中。
指令指針和堆棧指針寄存器是線程上下文中兩個最重要的寄存器。記住,線程總是在進程的上下文中運行的。因此,這些地址都用于標識擁有線程的進程地址空間中的內存。當線程的內核對象被初始化時,C O N T E X T結構的堆棧指針寄存器被設置為線程堆棧上用來放置p f n S t a r t -A d d r的地址。指令指針寄存器置為稱為B a s e T h r e a d S t a r t的未文檔化(和未輸出)的函數的地址中。該函數包含在K e r n e l 3 2 . d l l模塊中(這也是實現C r e a t e T h r e a d函數的地方) 。圖6 - 1顯示了它的全部情況。
下面是B a s e T h r e a d S t a r t函數執行的基本操作:
?
? ? 當線程完全初始化后,系統就要查看 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。如果該標志沒有傳遞,系統便將線程的暫停計數遞減為 0,該線程可以調度到一個進程中。然后系統用上次保存在線程上下文中的值加載到實際的 C P U寄存器中。這時線程就可以執行代碼,并對它的進程的地址空間中的數據進行操作。
? ? 由于新線程的指令指針被置為B a s e T h r e a d S t a r t,因此該函數實際上是線程開始執行的地方。B a s e T h r e a d S t a r t的原型會使你認為該函數接收了兩個參數,但是這表示該函數是由另一個函數來調用的,而實際情況并非如此。新線程只是在此處產生并且開始執行。 B a s e T h r e a d S t a r t認為它是由另一個函數調用的,因為它可以訪問兩個函數。但是,之所以可以訪問這些參數,是因為操作系統將值顯式寫入了線程的堆棧(這就是參數通常傳遞給函數的方法) 。注意,有些C P U結構使用C P U寄存器而不是堆棧來傳遞參數。對于這些結構來說,系統將在允許線程執行B a s e T h r e a d S t a r t函數之前對相應的寄存器正確地進行初始化。
? ? 當新線程執行B a s e T h r e a d S t a r t函數時,將會出現下列情況:
? 在線程函數中建立一個結構化異常處理(S E H)幀,這樣,在線程執行時產生的任何異常情況都會得到系統的某種默認處理(關于結構化異常處理的詳細說明參見第 2 3、2 4和2 5章) 。
? 系統調用線程函數,并將你傳遞給C r e a t e T h r e a d函數的p v P a r a m參數傳遞給它。
? 當線程函數返回時,B a s e T h r e a d S t a r t調用E x i t T h r e a d,并將線程函數的返回值傳遞給它。該線程內核對象的使用計數被遞減,線程停止執行。
? 如果線程產生一個沒有處理的異常條件,由B a s e T h r e a d S t a r t函數建立的S E H幀將負責處理該異常條件。通常情況下,這意味著向用戶顯示一個消息框,并且在用戶撤消該消息框時,BzsethreadStart調用ExitThread,以終止整個進程的運行,而不只是終止線程的運行。
注意,在B a s e T h r e a d S t a r t函數中,線程要么調用E x i t T h r e a d,要么調用E x i t P r o c e s s。這意味著線程不能退出該函數,它總是在函數中被撤消。這就是 B a s e T h r e a d S t a r t的原型規定返回V O I D,而它從來不返回的原因。
? ? 另外,由于使用B a s e T h r e a d S t a r t,線程函數可以在它完成處理后返回。當 B a s e T h r e a d S t a r t調用線程函數時,它會把返回地址推進堆棧,這樣,線程函數就能知道在何處返回。但是,B a s e T h r e a d S t a r t不允許返回。如果它不強制撤消線程,而只是試圖返回,那么幾乎可以肯定會引發訪問違規,因為線程堆棧上不存在返回地址,并且 B a s e T h r e a d S t a r t將試圖返回到某個隨機內存位置。
當進程的主線程被初始化時,它的指令指針被設置為另一個未文檔化的函數,稱為B a s e P r o c e s s S t a r t。該函數幾乎與B a s e T h r e a d S t a r t相同,形式類似下面的樣子:
?
? ? ?這兩個函數之間的唯一差別是,B a s e P r o c e s s S t a r t沒有引用p v P a r a m參數。當B a s e P r o c e s s S t a r t開始執行時,它調用 C / C + +運行期庫的啟動代碼,該啟動代碼先要初始化 m a i n、w m a i n、Wi n M a i n或w Wi n M a i n函數,然后調用這些函數。當進入點函數返回時, C / C + +運行期庫的啟動代碼就調用E x i t P r o c e s s。因此,對于C / C + +應用程序來說,主線程從不返回B a s e P r o c e s s S t a r t函數。
6.7 C/C++運行期庫的考慮
Visual C++配有6個C / C + +運行期庫。表6 - 1對它們進行了描述。
?
???當實現任何類型的編程項目時,必須知道將哪個庫與你的項目相鏈接。可以使用圖 6 - 2所示的Project Settings對話框來選定一個庫。在C / C + +選項卡上,在Code Generation(生成的代碼)類別中,從Use run-time library(使用運行期庫)組合框中選定6個選項中的一個。
?
? ? 應該考慮的第一件事情是, “為什么必須將一個庫用于單線程應用程序,而將另一個庫用于多線程應用程序?” ,原因是,標準C運行期庫是1 9 7 0年問世的,它遠遠早于線程在任何應用程序上的應用。運行期庫的發明者沒有考慮到將C運行期庫用于多線程應用程序的問題。
? ? 考慮一下標準C運行期的全局變量e r r n o。有些函數在發生錯誤時設置該變量。假設擁有下面這個代碼段:
?
? ? 現在,假設在調用s y s t e m函數之后和調用i f語句之前,執行上面代碼的線程中斷運行,同時假設,該線程中斷運行是為了讓同一進程中的第二個線程開始執行,而這個新線程將執行另一個負責設置全局變量 e r r n o的C運行期函數。當 C P U在晚些時候重新分配給第一個線程時,e r r n o的值將不再能夠反映調用上面代碼中的s y s t e m函數時的錯誤代碼。為了解決這個問題,每個線程都需要它自己的 e r r n o變量。此外,必須有一種機制,使得線程能夠引用它自己的 e r r n o變量,但是又不觸及另一個線程的e r r n o變量。
? ? 這是標準C / C + +運行期庫原先并不是設計用于多線程應用程序的唯一一個例子。在多線程環境中存在問題的C / C + +運行期庫變量和函數包括e r r n o、_ d o s e r r n o、s t r t o k、_ w c s t o k、s t r e r r o r、_ s t r e r r o r、t m p n a m、t m p f i l e、a s c t i m e、_ w a s c t i m e、g m t i m e、_ e c v t和_ f c v t等。
? ? 若要使多線程 C和C + +程序能夠正確地運行,必須創建一個數據結構,并將它與使用C / C + +運行期庫函數的每個線程關聯起來。當你調用 C / C + +運行期庫時,這些函數必須知道查看調用線程的數據塊,這樣就不會對別的線程產生不良影響。
那么系統是否知道在創建新線程時分配該數據塊呢?回答是它不知道。系統根本不知道你得到的應用程序是用C / C + +編寫的,也不知道你調用函數的線程本身是不安全的。問題在于你必須正確地進行所有的操作。若要創建一個新線程,絕對不要調用操作系統的 C r e a t e T h r e a d函數,必須調用C / C + +運行期庫函數_ b e g i n t h r e a d e x:
?
_ b e g i n t h r e a d e x函數的參數列表與C r e a t e T h r e a d函數的參數列表是相同的,但是參數名和類型并不完全相同。這是因為M i c r o s o f t的C / C + +運行期庫的開發小組認為,C / C + +運行期函數不應該對Wi n d o w s數據類型有任何依賴。_ b e g i n t h r e a d e x函數也像C r e a t e T h r e a d那樣,返回新創建的線程的句柄。因此,如果調用源代碼中的 C r e a t e T h r e a d,就很容易用對_ b e g i n t h r e a d e x的調用全局取代所有這些調用。不過,由于數據類型并不完全相同,所以必須進行某種轉換,使編譯器運行得順利些。為了使操作更加容易,我在源代碼中創建了一個宏 c h B E G I N T H R E A D E X:
?
? ? 注意,_ b e g i n t h r e a d e x函數只存在于C / C + +運行期庫的多線程版本中。如果鏈接到單線程運行期庫,就會得到一個鏈接程序報告的“未轉換的外部符號”錯誤消息。當然,從設計上講,這個錯誤的原因是單線程庫在多線程應用程序中不能正確地運行。另外需要注意,當創建一個新項目時,Visual Studio默認選定單線程庫。這并不是最安全的默認設置,對于多線程應用程序來說,必須顯式轉換到多線程的C / C + +運行期庫。
? ? 由于M i c r o s o f t為C / C + +運行期庫提供了源代碼,因此很容易準確地確定 C r e a t e T h r e a d究竟無法執行哪些 _ b e g i n t h r e a d e x能執行的操作。實際上,我搜索了 Visual Studio的光盤,發現_ b e g i n t h r e a d e x的源代碼在T h r e a d e x . c中。代換重新打印它的源代碼,這里提供了它的偽代碼版本,并且列出它的一些令人感興趣的要點:
?
? ? 下面是關于_ b e g i n t h r e a d e x的一些要點:
? 每個線程均獲得由C / C + +運行期庫的堆棧分配的自己的 t i d d a t a內存結構。 (t i d d a t a結構位于M t d l l . h文件中的Visual C++源代碼中) 。我在清單6 - 1中重建了它的結構。
? 傳遞給_ b e g i n t h r e a d e x的線程函數的地址保存在t i d d a t a內存塊中。傳遞給該函數的參數也保存在該數據塊中。
? _ b e g i n t h r e a d e x確實從內部調用C r e a t e T h r e a d,因為這是操作系統了解如何創建新線程的唯一方法。
? 當調用C r e a t e t T h r e a d時,它被告知通過調用_ t h r e a d s t a r t e x而不是p f n S t a r t A d d r來啟動執行新線程。還有,傳遞給線程函數的參數是t i d d a t a結構而不是p v P a r a m的地址。
? 如果一切順利,就會像 C r e a t e T h r e a d那樣返回線程句柄。如果任何操作失敗了,便返回N U L L。
既然為新線程指定了t i d d a t a結構,并且對該結構進行了初始化,那么必須了解該結構與線程之間是如何關聯起來的。讓我們觀察一下 _ t h r e a d s t a r t e x函數(它也位于 C / C + +運行期庫的T h r e a d e x . c文件中) 。這里是該函數的偽代碼版本:
?
?
? ? 下面是關于_ t h r e a d s t a r t e x的一些重點:
? 新線程開始從 B a s e t h r e a d S t a r t函數(在 k e r n e l 3 2 . d l l文件中)執行,然后轉移到_ t h r e a d s t a r t e x。
? 到達該新線程的t i d d a t a塊的地址作為其唯一參數被傳遞給_ t h r e a d s t a r t e x。
? T l s S e t Va l u e是個操作系統函數,負責將一個值與調用線程聯系起來。這稱為線程本地存儲器(T L S) ,將在第2 1章介紹。_ t h r e a d s t a r t e x函數將t i d d a t a塊與線程聯系起來。
? 一個S E H幀被放置在需要的線程函數周圍。這個幀負責處理與運行期庫相關的許多事情— 例如,運行期錯誤(比如放過了沒有抓住的 C + +異常條件)和 C / C + +運行期庫的s i g n a l函數。這是特別重要的。如果用 C r e a t e T h r e a d函數來創建線程,然后調用 C / C + +運行期庫的s i g n a l函數,那么該函數就不能正確地運行。
? 調用必要的線程函數,傳遞必要的參數。記住,函數和參數的地址由 _ b e g i n t h r e a d e x保存在t i d d a t a塊中。
? 必要的線程函數返回值被認為是線程的退出代碼。
注意, _ t h r e a d s t a r t e x并不只是返回到B a s e T h r e a d S t a r t。如果它準備這樣做,那么線程就終止運行,它的退出代碼將被正確地設置,但是線程的t i d d a t a內存塊不會被撤消。這將導致應用程序中出現一個漏洞。若要防止這個漏洞,可以調用另一個C / C + +運行期庫函數_ e n d t h r e a d e x ,并傳遞退出代碼。需要介紹的最后一個函數是 _ e n d t h r e a d e x(位于C運行期庫的T h r e a d e x . c文件中) 。下面是該函數的偽代碼版本:
?
?
? ? 下面是關于_ e n d t h r e a d e x的一些要點:
? C運行期庫的_ g e t p t d函數內部調用操作系統的 T l s G e t Va l u e函數,該函數負責檢索調用線程的t i d d a t a內存塊的地址。
? 然后該數據塊被釋放,而操作系統的E x i t T h r e a d函數被調用,以便真正撤消該線程。當然,退出代碼要正確地設置和傳遞。
本章前面說過,始終都應該設法避免使用 E x i t T h r e a d函數。這一點完全正確,我并不想收回我已經說過的話。ExitThread 函數將撤消調用函數,并且不允許它從當前執行的函數返回。由于該函數不能返回,所以創建的任何 C + +對象都不會被撤消。避免調用 E x i t T h r e a d的另一個原因是,它會使得線程的t i d d a t a內存塊無法釋放,這樣,應用程序將會始終占用內存(直到整個進程終止運行為止) 。
M i c r o s o f t的Visual C++開發小組認識到編程人員喜歡調用 E x i t T h r e a d,因此他們實現了他們的愿望,并且不會讓應用程序始終占用內存。如果真的想要強制撤消線程,可以讓它調用_ e n d t h r e a d e x(而不是調用E x i t T h r e a d)以便釋放線程的t i d d a t a塊,然后退出。不過建議不要調用_ e n d t h r e a d e x函數。
現在應該懂得為什么C / C + +運行期庫的函數需要為它創建的每個線程設置單獨的數據塊,同時,也應該了解如何通過調用 _ b e g i n t h r e a d e x來分配數據塊,再對它進行初始化,將該數據塊與你創建的線程聯系起來。你還應該懂得 _ e n d t h r e a d e x函數是如何在線程終止運行時釋放數據塊的。
一旦數據塊被初始化并且與線程聯系起來,線程調用的任何需要單線程實例數據的 C / C + +運行期庫函數都能很容易地(通過T l s G e t Va l u e)檢索調用線程的數據塊地址,并對線程的數據進行操作。這對于函數來說很好,但是你可能想知道它對 e r r n o之類的全局變量效果如何。E r r n o定義在標準的C頭文件中,類似下面的形式:
?
????如果創建一個多線程應用程序,必須在編譯器的命令行上設定 / M T(指多線程應用程序)或/ M D(指多線程D L L)開關。這將使編譯器能夠定義 _ M T標識符。然后,每當引用e r r n o時,實際上是調用內部的C / C + +運行期庫函數_ e r r n o。該函數返回調用線程的相關數據塊中的 e r r n o數據成員的地址。你將會發現, e r r n o宏被定義為獲取該地址的內容的宏。這個定義是必要的,
因為可以編寫類似下面形式的代碼:
?
????如果內部函數_ e r r n o只返回e r r n o的值,那么上面的代碼將不進行編譯。
多線程版本的C / C + +運行期庫還給某些函數設置了同步的基本要素。例如,如果兩個線程同時調用m a l l o c,那么內存堆棧就可能遭到破壞。多線程版本的 C / C + +運行期庫能夠防止兩個線程同時從堆棧中分配內存。為此,它要讓第二個線程等待,直到第一個線程從 m a l l o c返回。然后第二個線程才被允許進入(關于線程同步的問題將在第 8、9章和1 0章詳細介紹) 。
? ? 顯然,所有這些附加操作都會影響多線程版本的 C / C + +運行期庫的性能。這就是為什么M i c r o s o f t公司除了多線程版本外,還提供單線程版本的靜態鏈接的C / C + +運行期庫的原因。
C / C + +運行期庫的動態連接版本編寫成為一種通用版本。這樣它就可以被使用 C / C + +運行期庫函數的所有正在運行的應用程序和D L L共享。由于這個原因,運行期庫只存在于多線程版本中。由于D L L中提供了C / C + +運行期庫,因此應用程序(. e x e文件)和D L L不需要包含C / C + +運行期庫函數的代碼,結果它們的規模就比較小。另外,如果 M i c r o s o f t排除了C / C + +運行期庫
D L L中的錯誤,應用程序中的錯誤也會自動得到解決。
? ? 正如希望的那樣,C / C + +運行期庫的啟動代碼為應用程序的主線程分配了數據塊,并且對數據塊進行了初始化,這樣,主線程就能安全地調用 C / C + +運行期函數中的任何函數。當主線程從它的進入點函數返回時,C / C + +運行期庫就會釋放相關的數據塊。此外,啟動代碼設置了相應的結構化異常處理代碼,以便主線程能夠成功地調用C / C + +運行期庫的s i g n a l函數。
6.7.1 Oops — 錯誤地調用了C r e a t e T h r e a d
也許你想知道,如果調用C r e a t e T h r e a d,而不是調用C / C + +運行期庫的_ b e g i n t h r e a d e x來創建新線程,將會發生什么情況。當一個線程調用要求 t i d d a t a結構的C / C + +運行期庫函數時,將會發生下面的一些情況(大多數 C / C + +運行期庫函數都是線程安全函數,不需要該結構) 。首先,C / C + +運行期庫函數試圖 (通過調用 T l s G e t Va l u e )獲取線程的數據塊的地址。如果返回N U L L作為t i d d a t a塊的地址,調用線程就不擁有與該地址相關的t i d d a t a塊。這時,C / C + +運行期庫函數就在現場為調用線程分配一個 t i d d a t a塊,并對它進行初始化。然后該 t i d d a t a塊(通過T l s S e t Va l u e)與線程相關聯。此時,只要線程在運行,該 t i d d a t a將與線程待在一起。這時,C / C + +運行期庫函數就可以使用線程的 t i d d a t a塊,而且將來被調用的所有 C / C + +運行期函數也能使用t i d d a t a塊。
當然,這看來有些奇怪,因為線程運行時幾乎沒有任何障礙。不過,實際上還是存在一些問題。首先,如果線程使用 C / C + +運行期庫的s i g n a l函數,那么整個進程就會終止運行,因為結構化異常處理幀尚未準備好。第二,如果不是調用 _ e n d t h r e a d e x來終止線程的運行,那么數據塊就不會被撤消,內存泄漏就會出現(那么誰還為使用 C r e a t e T h r e a d函數創建的線程來調用_ e n d t h r e a d e x呢?) 。
注意 如果程序模塊鏈接到多線程D L L版本的C / C + +運行期庫,那么當線程終止運行并釋放 t i d d a t a塊(如果已經分配了 t i d d a t a塊的話)時,該運行期庫會收到一個D L L _ T H R E A D _ D E TA C H通知。盡管這可以防止t i d d a t a塊的泄漏,但是強烈建議使用_ b d g i n t h r e a d e x而不是使用C r e a t e t h r e a d來創建線程。
?
6.7.2 不應該調用的C / C + +運行期庫函數
C / C + +運行期庫也包含另外兩個函數:
unsigned long _begininthread()和 void _endthread(void);
???創建這兩個函數的目的是用來執行_ b e g i n t h r e a d e x和_ e n d t h r e a d e x函數的功能。但是,如你所見,_ b e g i n t h r e a d函數的參數比較少,因此比特性全面的_ b e g i n t h r e a d e x函數受到更大的限制。例如,如果使用_ b e g i n t h r e a d,就無法創建帶有安全屬性的新線程,無法創建暫停的線程,也
無法獲得線程的I D值。_ e n d t h r e a d函數的情況與之類似。它不帶參數,這意味著線程的退出代碼必須硬編碼為0。
e n d t h r e a d函數還存在另一個很難注意到的大問題。在_ e n d t h r e a d調用E x i t T h r e a d之前,它調用C l o s e H a n d l e,傳遞新線程的句柄。若要了解為什么這是個大問題,請看下面的代碼:
?
新創建的線程可以在第一個線程調用G e t E x i t C o d e T h r e a d之前運行、返回和終止。如果出現這種情況,h T h r e a d中的值將無效,因為_ e n d t h r e a d已經關閉了新線程的句柄。不用說,由于相同的原因,對C l o s e H a n d l e的調用也將失敗。
新的_ e n d t h r e a d e x函數并不關閉線程的句柄,因此,如果用調用 b e g i n t h r e a d e x來取代調用_ b e g i n t h r e a d,那么上面的代碼段將能正確運行。記住,當線程函數返回時, _ b e g i n t h r t e a d e x調用_ e n d t h r e a d e x,而_ b e g i n t h r e a d則調用_ e n d t h r e a d。
6.8 對自己的I D概念應該有所了解
? ? 當線程運行時,它們常常想要調用Wi n d o w s函數來改變它們的運行環境。例如,線程可能想要改變它的優先級或它的進程的優先級(優先級將在第 7章中介紹) 。由于線程常常要改變它的(或它的進程的)環境,因此Wi n d o w s提供了一些函數,使線程能夠很容易引用它的進程內核對象,或者引用它自己的線程內核對象:
DWORD GetCurrentProcessId();
DWORD GetCurrentThreadId();
? ? 上面這兩個函數都能返回調用線程的進程的偽句柄或線程內核對象的偽句柄。這些函數并不在創建進程的句柄表中創建新句柄。還有,調用這些函數對進程或線程內核對象的使用計數沒有任何影響。如果調用 C l o s e H a n d l e,將偽句柄作為參數來傳遞,那么 C l o s e H a n d l e就會忽略該函數的調用并返回FA L S E。
? ? 當調用一個需要進程句柄或線程句柄的 Wi n d o w s函數時,可以傳遞一個偽句柄,使該函數執行它對調用進程或線程的操作。例如,通過調用下面的 G e t P r o c e s s Ti m e s函數,線程可以查詢它的進程的時間使用情況:
?
? ? 同樣,通過調用G e t T h r e a d Ti m e s函數,線程可以查詢它自己的線程時間:
?
? ? 少數Wi n d o w s函數允許用進程或線程在系統范圍內獨一無二的 I D來標識某個進程或線程。
下面這兩個函數使得線程能夠查詢它的進程的唯一I D或它自己的唯一I D:
DWORD GetCurrentProcessId();
DWORD GetCurrentThreadId();
這兩個函數通常不像能夠返回偽句柄的函數那樣有用,但是有的時候用起來還是很方便的。
將偽句柄轉換為實句柄
有時可能需要獲得線程的實句柄而不是它的偽句柄。所謂“實句柄” ,我是指用來明確標識一個獨一無二的線程的句柄。請看下面的代碼:
?
????你能發現這個代碼段存在的問題嗎?這個代碼的目的是讓父線程給子線程傳遞一個線程句柄,以標識父線程。但是,父線程傳遞了一個偽句柄,而不是一個實句柄。當子線程開始運行時,它將一個偽句柄傳遞給 G e t T h r e a d Ti m e函數,使子線程得到它自己的 C P U時間,而不是父線程的C P U時間。出現這種情況的原因是線程的偽句柄是當前線程的句柄,也就是說,它是調用函數的線程的句柄。
為了修改這個代碼,必須將偽句柄變成實句柄。D u p l i c a t e H a n d l e函數能夠執行這一轉換:
?
通常可以使用這個函數, 用與另一個進程相關的內核對象來創建一個與進程相關的新句柄。然而,可以用一種特殊的方法來使用這個函數,以便修改上面介紹的代碼段。正確的代碼段應該是下面的樣子:
?
? ? 當父線程運行時,它就將標識父線程所用的偽句柄轉換成明確標識父線程所用的新的實句柄。同時它將這個實句柄傳遞 ? ?給C r e a t e T h r e a d。當子線程啟動運行時,它的p v P a r a m參數包含了線程的實句柄。對傳遞該句柄的函數的任何調用都將影響父線程而不是子線程。
? ? 由于D u p l i c a t e H a n d l e會遞增特定對象的使用計數,因此當完成對復制對象句柄的使用時,應該將目標句柄傳遞給 C l o s e H a n d l e,從而遞減對象的使用計數,這一點很重要。上面的代碼段已經顯示出這一點。在調用G e t T h r e a d Ti m e s之后,緊接著子線程調用C l o s e H a n d l e,以便遞減父線程對象的使用計數。在這個代碼段中,我假設子線程不使用該句柄來調用任何其他函數。如果其他函數被調用,以便傳遞父線程的句柄,那么在子線程不再需要該句柄之前,不應該調用C l o s e H a n d l e.
? ? 還要指出,D u p l i c a t e H a n d l e可以用來將進程的偽句柄轉換成進程的實句柄,如下面的代碼所示:
?
總結
以上是生活随笔為你收集整理的Windows核心编程 第六章 线程基础知识 (下)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Windows核心编程 第六章 线程基础
- 下一篇: Windows Pe 第三章 PE头文