




版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進(jìn)行舉報或認(rèn)領(lǐng)
文檔簡介
1、Windows 下的臨界區(qū)中的代碼死鎖解開Windows下的臨界區(qū)中的代碼死鎖2011-06-08 10:03解開Windows下的臨界區(qū)中的代碼死鎖發(fā)布日期:1/13/2005|更新日期:1/13/2005原文:Pietrek和Russ Osterlund本文假定您熟悉Win32、C+和多線程處理。下載本文的代碼:CriticalSections.exe(415KB)摘要臨界區(qū)是一種防止多個線程同時執(zhí)行一個特定代碼節(jié)的機(jī)制,這一主題并沒有引起太多關(guān)注,因而人們未能對其深刻理解。在需要跟蹤代碼中的多線程處理的性能時,對Windows中臨界區(qū)的深刻理解非常有用。本文深入研究臨界區(qū)的原理,以揭示在
2、查找死鎖和確認(rèn)性能問題過程中的有用信息。它還包含一個便利的實用工具程序,可以顯示所有臨界區(qū)及其當(dāng)前狀態(tài)。在我們許多年的編程實踐中,對于Win32臨界區(qū)沒有受到非常多的under the hood關(guān)注而感到非常奇怪。當(dāng)然,您可能了解有關(guān)臨界區(qū)初始化與使用的基礎(chǔ)知識,但您是否曾經(jīng)花費(fèi)時間來深入研究WINNT.H中所定義的CRITICAL_SECTION結(jié)構(gòu)呢?在這一結(jié)構(gòu)中有一些非常有意義的好東西被長期忽略。我們將對此進(jìn)行補(bǔ)充,并向您介紹一些很有意義的技巧,這些技巧對于跟蹤那些難以察覺的多線程處理錯誤非常有用。更重要的是,使用我們的MyCriticalSections實用工具,可以明白如何對CRIT
3、ICAL_SECTION進(jìn)行微小地擴(kuò)展,以提供非常有用的特性,這些特性可用于調(diào)試和性能調(diào)整(要下載完整代碼,參見本文頂部的鏈接)。老實說,作者們經(jīng)常忽略CRITICAL_SECTION結(jié)構(gòu)的部分原因在于它在以下兩個主要Win32代碼庫中的實現(xiàn)有很大不同:MicrosoftWindows95和Windows NT。人們知道這兩種代碼庫都已經(jīng)發(fā)展出大量后續(xù)版本(其最新版本分別為Windows Me和Windows XP),但沒有必要在此處將其一一列出。關(guān)鍵在于Windows XP現(xiàn)在已經(jīng)發(fā)展得非常完善,開發(fā)商可能很快就會停止對Windows 95系列操作系統(tǒng)的支持。我們在本文中就是這么做的。誠然,
4、當(dāng)今最受關(guān)注的是Microsoft.NET Framework,但是良好的舊式Win32編程不會很快消失。如果您擁有采用了臨界區(qū)的現(xiàn)有Win32代碼,您會發(fā)現(xiàn)我們的工具以及對臨界區(qū)的說明都非常有用。但是請注意,我們只討論Windows NT及其后續(xù)版本,而沒有涉及與.NET相關(guān)的任何內(nèi)容,這一點非常重要。臨界區(qū):簡述如果您非常熟悉臨界區(qū),并可以不假思索地進(jìn)行應(yīng)用,那就可以略過本節(jié)。否則,請向下閱讀,以對這些內(nèi)容進(jìn)行快速回顧。如果您不熟悉這些基礎(chǔ)內(nèi)容,則本節(jié)之后的內(nèi)容就沒有太大意義。臨界區(qū)是一種輕量級機(jī)制,在某一時間內(nèi)只允許一個線程執(zhí)行某個給定代碼段。通常在修改全局?jǐn)?shù)據(jù)(如集合類)時會使用臨界區(qū)
5、。事件、多用戶終端執(zhí)行程序和信號量也用于多線程同步,但臨界區(qū)與它們不同,它并不總是執(zhí)行向內(nèi)核模式的控制轉(zhuǎn)換,這一轉(zhuǎn)換成本昂貴。稍后將會看到,要獲得一個未占用臨界區(qū),事實上只需要對內(nèi)存做出很少的修改,其速度非???。只有在嘗試獲得已占用臨界區(qū)時,它才會跳至內(nèi)核模式。這一輕量級特性的缺點在于臨界區(qū)只能用于對同一進(jìn)程內(nèi)的線程進(jìn)行同步。臨界區(qū)由WINNT.H中所定義的RTL_CRITICAL_SECTION結(jié)構(gòu)表示。因為您的C+代碼通常聲明一個CRITICAL_SECTION類型的變量,所以您可能對此并不了解。研究WINBASE.H后您會發(fā)現(xiàn):typedef RTL_CRITICAL_SECTION C
6、RITICAL_SECTION;我們將在短時間內(nèi)揭示RTL_CRITICAL_SECTION結(jié)構(gòu)的實質(zhì)。此時,重要問題在于CRITICAL_SECTION(也稱作RTL_CRITICAL_SECTION)只是一個擁有易訪問字段的結(jié)構(gòu),這些字段可以由KERNEL32 API操作。在將臨界區(qū)傳遞給InitializeCriticalSection時(或者更準(zhǔn)確地說,是在傳遞其地址時),臨界區(qū)即開始存在。初始化之后,代碼即將臨界區(qū)傳遞給EnterCriticalSection和LeaveCriticalSection API。一個線程自EnterCriticalSection中返回后,所有其他調(diào)用E
7、nterCriticalSection的線程都將被阻止,直到第一個線程調(diào)用LeaveCriticalSection為止。最后,當(dāng)不再需要該臨界區(qū)時,一種良好的編碼習(xí)慣是將其傳遞給DeleteCriticalSection。在臨界區(qū)未被使用的理想情況中,對EnterCriticalSection的調(diào)用非??焖伲驗樗皇亲x取和修改用戶模式內(nèi)存中的內(nèi)存位置。否則(在后文將會遇到一種例外情況),阻止于臨界區(qū)的線程有效地完成這一工作,而不需要消耗額外的CPU周期。所阻止的線程以內(nèi)核模式等待,在該臨界區(qū)的所有者將其釋放之前,不能對這些線程進(jìn)行調(diào)度。如果有多個線程被阻止于一個臨界區(qū)中,當(dāng)另一線程釋放該臨界
8、區(qū)時,只有一個線程獲得該臨界區(qū)。深入研究:RTL_CRITICAL_SECTION結(jié)構(gòu)即使您已經(jīng)在日常工作中使用過臨界區(qū),您也非??赡懿]有真正了解超出文檔之外的內(nèi)容。事實上存在著很多非常容易掌握的內(nèi)容。例如,人們很少知道一個進(jìn)程的臨界區(qū)是保存于一個鏈表中,并且可以對其進(jìn)行枚舉。實際上,WINDBG支持!locks命令,這一命令可以列出目標(biāo)進(jìn)程中的所有臨界區(qū)。我們稍后將要談到的實用工具也應(yīng)用了臨界區(qū)這一鮮為人知的特征。為了真正理解這一實用工具如何工作,有必要真正掌握臨界區(qū)的內(nèi)部結(jié)構(gòu)。記著這一點,現(xiàn)在開始研究RTL_CRITICAL_SECTION結(jié)構(gòu)。為方便起見,將此結(jié)構(gòu)列出如下:struct
9、 RTL_CRITICAL_SECTIONPRTL_CRITICAL_SECTION_DEBUG DebugInfo;LONG LockCount;LONG RecursionCount;HANDLE OwningThread;HANDLE LockSemaphore;ULONG_PTR SpinCount;以下各段對每個字段進(jìn)行說明。DebugInfo此字段包含一個指針,指向系統(tǒng)分配的伴隨結(jié)構(gòu),該結(jié)構(gòu)的類型為RTL_CRITICAL_SECTION_DEBUG。這一結(jié)構(gòu)中包含更多極有價值的信息,也定義于WINNT.H中。我們稍后將對其進(jìn)行更深入地研究。LockCount這是臨界區(qū)中最重要的一
10、個字段。它被初始化為數(shù)值-1;此數(shù)值等于或大于0時,表示此臨界區(qū)被占用。當(dāng)其不等于-1時,OwningThread字段(此字段被錯誤地定義于WINNT.H中-應(yīng)當(dāng)是DWORD而不是HANDLE)包含了擁有此臨界區(qū)的線程ID。此字段與(RecursionCount-1)數(shù)值之間的差值表示有多少個其他線程在等待獲得該臨界區(qū)。RecursionCount此字段包含所有者線程已經(jīng)獲得該臨界區(qū)的次數(shù)。如果該數(shù)值為零,下一個嘗試獲取該臨界區(qū)的線程將會成功。OwningThread此字段包含當(dāng)前占用此臨界區(qū)的線程的線程標(biāo)識符。此線程ID與GetCurrentThreadId之類的API所返回的ID相同。Lo
11、ckSemaphore此字段的命名不恰當(dāng),它實際上是一個自復(fù)位事件,而不是一個信號。它是一個內(nèi)核對象句柄,用于通知操作系統(tǒng):該臨界區(qū)現(xiàn)在空閑。操作系統(tǒng)在一個線程第一次嘗試獲得該臨界區(qū),但被另一個已經(jīng)擁有該臨界區(qū)的線程所阻止時,自動創(chuàng)建這樣一個句柄。應(yīng)當(dāng)調(diào)用DeleteCriticalSection(它將發(fā)出一個調(diào)用該事件的CloseHandle調(diào)用,并在必要時釋放該調(diào)試結(jié)構(gòu)),否則將會發(fā)生資源泄漏。SpinCount僅用于多處理器系統(tǒng)。MSDN文檔對此字段進(jìn)行如下說明:在多處理器系統(tǒng)中,如果該臨界區(qū)不可用,調(diào)用線程將在對與該臨界區(qū)相關(guān)的信號執(zhí)行等待操作之前,旋轉(zhuǎn)dwSpinCount次。如果該
12、臨界區(qū)在旋轉(zhuǎn)操作期間變?yōu)榭捎?,該調(diào)用線程就避免了等待操作。旋轉(zhuǎn)計數(shù)可以在多處理器計算機(jī)上提供更佳性能,其原因在于在一個循環(huán)中旋轉(zhuǎn)通常要快于進(jìn)入內(nèi)核模式等待狀態(tài)。此字段默認(rèn)值為零,但可以用InitializeCriticalSectionAndSpinCount API將其設(shè)置為一個不同值。RTL_CRITICAL_SECTION_DEBUG結(jié)構(gòu)前面我們注意到,在RTL_CRITICAL_SECTION結(jié)構(gòu)內(nèi),DebugInfo字段指向一個RTL_CRITICAL_SECTION_DEBUG結(jié)構(gòu),該結(jié)構(gòu)給出如下:struct _RTL_CRITICAL_SECTION_DEBUGWORD Typ
13、e;WORD CreatorBackTraceIndex;RTL_CRITICAL_SECTION*CriticalSection;LIST_ENTRY ProcessLocksList;DWORD EntryCount;DWORD ContentionCount;DWORD Spare2;這一結(jié)構(gòu)由InitializeCriticalSection分配和初始化。它既可以由NTDLL內(nèi)的預(yù)分配數(shù)組分配,也可以由進(jìn)程堆分配。RTL_CRITICAL_SECTION的這一伴隨結(jié)構(gòu)包含一組匹配字段,具有迥然不同的角色:有兩個難以理解,隨后兩個提供了理解這一臨界區(qū)鏈結(jié)構(gòu)的關(guān)鍵,兩個是重復(fù)設(shè)置的,最后兩
14、個未使用。下面是對RTL_CRITICAL_SECTION字段的說明。Type此字段未使用,被初始化為數(shù)值0。CreatorBackTraceIndex此字段僅用于診斷情形中。在注冊表項HKLMSoftwareMicrosoftWindows NTCurrentVersionImage File Execution OptionsYourProgram之下是keyfield、GlobalFlag和StackTraceDatabaseSizeInMb值。注意,只有在運(yùn)行稍后說明的Gflags命令時才會顯示這些值。這些注冊表值的設(shè)置正確時,CreatorBackTraceIndex字段將由堆棧跟蹤
15、中所用的一個索引值填充。在MSDN中搜索GFlags文檔中的短語create user mode stack trace database和enlarging the user-mode stack trace database,可以找到有關(guān)這一內(nèi)容的更多信息。CriticalSection指向與此結(jié)構(gòu)相關(guān)的RTL_CRITICAL_SECTION。圖1說明該基礎(chǔ)結(jié)構(gòu)以及RTL_CRITICAL_SECTION、RTL_CRITICAL_SECTION_DEBUG和事件鏈中其他參與者之間的關(guān)系。圖1臨界區(qū)處理流程ProcessLocksList LIST_ENTRY是用于表示雙向鏈表中節(jié)點的標(biāo)
16、準(zhǔn)Windows數(shù)據(jù)結(jié)構(gòu)。RTL_CRITICAL_SECTION_DEBUG包含了鏈表的一部分,允許向前和向后遍歷該臨界區(qū)。本文后面給出的實用工具說明如何使用Flink(前向鏈接)和Blink(后向鏈接)字段在鏈表中的成員之間移動。任何從事過設(shè)備驅(qū)動程序或者研究過Windows內(nèi)核的人都會非常熟悉這一數(shù)據(jù)結(jié)構(gòu)。EntryCount/ContentionCount這些字段在相同的時間、出于相同的原因被遞增。這是那些因為不能馬上獲得臨界區(qū)而進(jìn)入等待狀態(tài)的線程的數(shù)目。與LockCount和RecursionCount字段不同,這些字段永遠(yuǎn)都不會遞減。Spares這兩個字段未使用,甚至未被初始化(盡
17、管在刪除臨界區(qū)結(jié)構(gòu)時將這些字段進(jìn)行了清零)。后面將會說明,可以用這些未被使用的字段來保存有用的診斷值。即使RTL_CRITICAL_SECTION_DEBUG中包含多個字段,它也是常規(guī)臨界區(qū)結(jié)構(gòu)的必要成分。事實上,如果系統(tǒng)恰巧不能由進(jìn)程堆中獲得這一結(jié)構(gòu)的存儲區(qū),InitializeCriticalSection將返回為STATUS_NO_MEMORY的LastError結(jié)果,然后返回處于不完整狀態(tài)的臨界區(qū)結(jié)構(gòu)。臨界區(qū)狀態(tài)當(dāng)程序執(zhí)行、進(jìn)入與離開臨界區(qū)時,RTL_CRITICAL_SECTION和RTL_CRITICAL_SECTION_DEBUG結(jié)構(gòu)中的字段會根據(jù)臨界區(qū)所處的狀態(tài)變化。這些字段由
18、臨界區(qū)API中的簿記代碼更新,在后面將會看到這一點。如果程序為多線程,并且其線程訪問是由臨界區(qū)保護(hù)的公用資源,則這些狀態(tài)就更有意義。但是,不管代碼的線程使用情況如何,有兩種狀態(tài)都會出現(xiàn)。第一種情況,如果LockCount字段有一個不等于-1的數(shù)值,此臨界區(qū)被占用,OwningThread字段包含擁有該臨界區(qū)的線程的線程標(biāo)識符。在多線程程序中,LockCount與RecursionCount聯(lián)合表明當(dāng)前有多少線程被阻止于該臨界區(qū)。第二種情況,如果RecursionCount是一個大于1的數(shù)值,其告知您所有者線程已經(jīng)重新獲得該臨界區(qū)多少次(也許不必要),該臨界區(qū)既可以通過調(diào)用EnterCritic
19、alSection、也可以通過調(diào)用TryEnterCriticalSection獲得。大于1的任何數(shù)值都表示代碼的效率可能較低或者可能在以后發(fā)生錯誤。例如,訪問公共資源的任何C+類方法可能會不必要地重新進(jìn)入該臨界區(qū)。注意,在大多數(shù)時間里,LockCount與RecursionCount字段中分別包含其初始值-1和0,這一點非常重要。事實上,對于單線程程序,不能僅通過檢查這些字段來判斷是否曾獲得過臨界區(qū)。但是,多線程程序留下了一些標(biāo)記,可以用來判斷是否有兩個或多個線程試圖同時擁有同一臨界區(qū)。您可以找到的標(biāo)記之一是即使在該臨界區(qū)未被占用時LockSemaphore字段中仍包含一個非零值。這表示:在
20、某一時間,此臨界區(qū)阻止了一個或多個線程-事件句柄用于通知該臨界區(qū)已被釋放,等待該臨界區(qū)的線程之一現(xiàn)在可以獲得該臨界區(qū)并繼續(xù)執(zhí)行。因為OS在臨界區(qū)阻止另一個線程時自動分配事件句柄,所以如果您在不再需要臨界區(qū)時忘記將其刪除,LockSemaphore字段可能會導(dǎo)致程序中發(fā)生資源泄漏。在多線程程序中可能遇到的另一狀態(tài)是EntryCount和ContentionCount字段包含一個大于零的數(shù)值。這兩個字段保存有臨界區(qū)對一個線程進(jìn)行阻止的次數(shù)。在每次發(fā)生這一事件時,這兩個字段被遞增,但在臨界區(qū)存在期間不會被遞減。這些字段可用于間接確定程序的執(zhí)行路徑和特性。例如,EntryCount非常高時則意味著該臨
21、界區(qū)經(jīng)歷著大量爭用,可能會成為代碼執(zhí)行過程中的一個潛在瓶頸。在研究一個死鎖程序時,還會發(fā)現(xiàn)一種似乎無法進(jìn)行邏輯解釋的狀態(tài)。一個使用非常頻繁的臨界區(qū)的LockCount字段中包含一個大于-1的數(shù)值,也就是說它被線程所擁有,但是OwningThread字段為零(這樣就無法找出是哪個線程導(dǎo)致問題)。測試程序是多線程的,在單處理器計算機(jī)和多處理器計算機(jī)中都會出現(xiàn)這種情況。盡管LockCount和其他值在每次運(yùn)行中都不同,但此程序總是死鎖于同一臨界區(qū)。我們非常希望知道是否有任何其他開發(fā)人員也遇到了導(dǎo)致這一狀態(tài)的API調(diào)用序列。構(gòu)建一個更好的捕鼠器在我們學(xué)習(xí)臨界區(qū)的工作方式時,非常偶然地得到一些重要發(fā)現(xiàn),
22、利用這些發(fā)現(xiàn)可以得到一個非常好的實用工具。第一個發(fā)現(xiàn)是ProcessLocksList LIST_ENTRY字段的出現(xiàn),這使我們想到進(jìn)程的臨界區(qū)可能是可枚舉的。另一個重大發(fā)現(xiàn)是我們知道了如何找出臨界區(qū)列表的頭。還有一個重要發(fā)現(xiàn)是可以在沒有任何損失的情況下寫RTL_CRITICAL_SECTION的Spare字段(至少在我們的所有測試中如此)。我們還發(fā)現(xiàn)可以很容易地重寫系統(tǒng)的一些臨界區(qū)例程,而不需要對源文件進(jìn)行任何修改。最初,我們由一個簡單的程序開始,其檢查一個進(jìn)程中的所有臨界區(qū),并列出其當(dāng)前狀態(tài),以查看是否擁有這些臨界區(qū)。如果擁有,則找出由哪個線程擁有,以及該臨界區(qū)阻止了多少個線程?這種做法對
23、于OS的狂熱者們比較適合,但對于只是希望有助于理解其程序的典型的程序員就不是非常有用了。即使是在最簡單的控制臺模式Hello World程序中也存在許多臨界區(qū)。其中大部分是由USER32或GDI32之類的系統(tǒng)DLL創(chuàng)建,而這些DLL很少會導(dǎo)致死鎖或性能問題。我們希望有一種方法能濾除這些臨界區(qū),而只留下代碼中所關(guān)心的那些臨界區(qū)。RTL_CRITICAL_SECTION_DEBUG結(jié)構(gòu)中的Spare字段可以很好地完成這一工作??梢允褂闷渲械囊粋€或兩個來指示:這些臨界區(qū)是來自用戶編寫的代碼,而不是來自O(shè)S。于是,下一個邏輯問題就變?yōu)槿绾未_定哪些臨界區(qū)是來自您編寫的代碼。有些讀者可能還記得Matt P
24、ietrek 2001年1月的Under The Hood專欄中的LIBCTINY.LIB。LIBCTINY所采用的一個技巧是一個LIB文件,它重寫了關(guān)鍵Visual C+運(yùn)行時例程的標(biāo)準(zhǔn)實現(xiàn)。將LIBCTINY.LIB文件置于鏈接器行的其他LIB之前,鏈接器將使用這一實現(xiàn),而不是使用Microsoft所提供的導(dǎo)入庫中的同名后續(xù)版本。為對臨界區(qū)應(yīng)用類似技巧,我們創(chuàng)建InitializeCriticalSection的一個替代版本及其相關(guān)導(dǎo)入庫。將此LIB文件置于KERNEL32.LIB之前,鏈接器將鏈接我們的版本,而不是KERNEL32中的版本。對InitializeCriticalSecti
25、on的實現(xiàn)顯示在圖2中。此代碼在概念上非常簡單。它首先調(diào)用KERNEL32.DLL中的實際InitializeCriticalSection。接下來,它獲得調(diào)用InitializeCriticalSection的代碼地址,并將其貼至RTL_CRITICAL_SECTION_DEBUG結(jié)構(gòu)的備用字段之一。我們的代碼如何確定調(diào)用代碼的地址呢?x86 CALL指令將返回地址置于堆棧中。CriticalSectionHelper代碼知道該返回地址位于堆棧幀中一個已知的固定位置。實際結(jié)果是:與CriticalSectionHelper.lib正確鏈接的任何EXE或DLL都將導(dǎo)入我們的DLL(Critic
26、alSectionHelper.DLL),并占用應(yīng)用了備用字段的臨界區(qū)。這樣就使事情簡單了許多。現(xiàn)在我們的實用工具可以簡單地遍歷進(jìn)程中的所有臨界區(qū),并且只顯示具有正確填充的備用字段的臨界區(qū)信息。那么需要為這一實用工具付出什么代價呢?請稍等,還有更多的內(nèi)容!因為您的所有臨界區(qū)現(xiàn)在都包含對其進(jìn)行初始化時的地址,實用工具可以通過提供其初始化地址來識別各個臨界區(qū)。原始代碼地址本身沒有那么有用。幸運(yùn)的是,DBGHELP.DLL使代碼地址向源文件、行號和函數(shù)名稱的轉(zhuǎn)換變得非常容易。即使一個臨界區(qū)中沒有您在其中的簽名,也可以將其地址提交給DBGHELP.DLL。如果將其聲明為一個全局變量,并且如果符號可用,
27、則您就可以在原始源代碼中確定臨界區(qū)的名稱。順便說明一下,如果通過設(shè)置_NT_SYMBOL_PATH環(huán)境變量,并設(shè)置DbgHelp以使用其Symbol Server下載功能,從而使DbgHelp發(fā)揮其效用,則會得到非常好的結(jié)果。MyCriticalSections實用工具我們將所有這些思想結(jié)合起來,提出了MyCriticalSections程序。MyCriticalSections是一個命令行程序,在不使用參數(shù)運(yùn)行該程序時可以看到一些選項:Syntax:MyCriticalSections PIDoptionsOptions:/a=all critical sections/e=show onl
28、y entered critical sections/v=verbose唯一需要的參數(shù)是Program ID或PID(十進(jìn)制形式)??梢杂枚喾N方法獲得PID,但最簡單的方法可能就是通過Task Manager。在沒有其他選項時,MyCriticalSections列出了來自代碼模塊的所有臨界區(qū)狀態(tài),您已經(jīng)將CriticalSectionHelper.DLL鏈接至這些代碼模塊。如果有可用于這一(些)模塊的符號,代碼將嘗試提供該臨界區(qū)的名稱,以及對其進(jìn)行初始化的位置。要查看MyCriticalSections是如何起作用的,請運(yùn)行Demo.EXE程序,該程序包含在下載文件中。Demo.EXE只是
29、初始化兩個臨界區(qū),并由一對線程進(jìn)入這兩個臨界區(qū)。圖3顯示運(yùn)行MyCriticalSections 2040的結(jié)果(其中2040為Demo.EXE的PID)。在該圖中,列出了兩個臨界區(qū)。在本例中,它們被命名為csMain和yetAnotherCriticalSection。每個Address:行顯示了CRITICAL_SECTION的地址及其名稱。Initialized in行包含了在其中初始化CRITICAL_SECTION的函數(shù)名。代碼的Initialized at行顯示了源文件和初始化函數(shù)中的行號。對于csMain臨界區(qū),您將看到鎖定數(shù)為0、遞歸數(shù)為1,表示一個已經(jīng)被一線程獲得的臨界區(qū),并
30、且沒有其他線程在等待該臨界區(qū)。因為從來沒有線程被阻止于該臨界區(qū),所以Entry Count字段為0?,F(xiàn)在來看yetAnotherCriticalSection,會發(fā)現(xiàn)其遞歸數(shù)為3??焖贋g覽Demo代碼可以看出:主線程調(diào)用EnterCriticalSection三次,所以事情的發(fā)生與預(yù)期一致。但是,還有一個第二線程試圖獲得該臨界區(qū),并且已經(jīng)被阻止。同樣,LockCount字段也為3。此輸出顯示有一個等待線程。MyCriticalSections擁有一些選項,使其對于更為勇敢的探索者非常有用。/v開關(guān)顯示每個臨界區(qū)的更多信息。旋轉(zhuǎn)數(shù)與鎖定信號字段尤為重要。您經(jīng)常會看到NTDLL和其他DLL擁有一些
31、旋轉(zhuǎn)數(shù)非零的臨界區(qū)。如果一個線程在獲得臨界區(qū)的過程中曾被鎖定,則鎖定信號字段為非零值。/v開關(guān)還顯示了RTL_CRITICAL_SECTION_DEBUG結(jié)構(gòu)中備用字段的內(nèi)容。/a開關(guān)顯示進(jìn)程中的所有臨界區(qū),即使其中沒有CriticalSectionHelper.DLL簽名也會顯示。如果使用/a,則請做好有大量輸出的準(zhǔn)備。真正的黑客希望同時使用/a和/v,以顯示進(jìn)程中全部內(nèi)容的最多細(xì)節(jié)。使用/a的一個小小的好處是會看到NTDLL中的LdrpLoaderLock臨界區(qū)。此臨界區(qū)在DllMain調(diào)用和其他一些重要時間內(nèi)被占用。LdrpLoaderLock是許多不太明顯、表面上難以解釋的死鎖的形成原
32、因之一。(為使MyCriticalSection能夠正確標(biāo)記LdrpLoaderLock實例,需要用于NTDLL的PDB文件可供使用。)/e開關(guān)使程序僅顯示當(dāng)前被占用的臨界區(qū)。未使用/a開關(guān)時,只顯示代碼中被占用的臨界區(qū)(如備用字段中的簽名所指示)。采用/a開關(guān)時,將顯示進(jìn)程中的全部被占用臨界區(qū),而不考慮其來源。那么,希望什么時候運(yùn)行MyCriticalSections呢?一個很明確的時間是在程序被死鎖時。檢查被占用的臨界區(qū),以查看是否有什么使您驚訝的事情。即使被死鎖的程序正運(yùn)行于調(diào)試器的控制之下,也可以使用MyCriticalSections。另一種使用MyCriticalSections的
33、時機(jī)是在對有大量多線程的程序進(jìn)行性能調(diào)整時。在阻塞于調(diào)試器中的一個使用頻繁、非重入函數(shù)時,運(yùn)行MyCriticalSections,查看在該時刻占用了哪些臨界區(qū)。如果有很多線程都執(zhí)行相同任務(wù),就非常容易導(dǎo)致一種情形:一個線程的大部分時間被消耗在等待獲得一個使用頻繁的臨界區(qū)上。如果有多個使用頻繁的臨界區(qū),這造成的后果就像花園的澆水軟管打了結(jié)一樣。解決一個爭用問題只是將問題轉(zhuǎn)移到下一個容易造成阻塞的臨界區(qū)。一個查看哪些臨界區(qū)最容易導(dǎo)致爭用的好方法是在接近程序結(jié)尾處設(shè)置一個斷點。在遇到斷點時,運(yùn)行MyCriticalSections并查找具有最大Entry Count值的臨界區(qū)。正是這些臨界區(qū)導(dǎo)致了
34、大多數(shù)阻塞和線程轉(zhuǎn)換。盡管MyCriticalSections運(yùn)行于Windows 2000及更新版本,但您仍需要一個比較新的DbgHelp.DLL版本-5.1版或更新版本。Windows XP中提供這一版本。也可以由其他使用DbgHelp的工具中獲得該版本。例如,Debugging Tools For Windows下載中通常擁有最新的DbgHelp.DLL。深入研究重要的臨界區(qū)例程此最后一節(jié)是為那些希望理解臨界區(qū)實現(xiàn)內(nèi)幕的勇敢讀者提供的。對NTDLL進(jìn)行仔細(xì)研究后可以為這些例程及其支持子例程創(chuàng)建偽碼(見下載中的NTDLL(CriticalSections).cpp)。以下KERNEL32
35、API組成臨界區(qū)的公共接口:InitializeCriticalSection InitializeCriticalSectionAndSpinCount DeleteCriticalSection TryEnterCriticalSection EnterCriticalSection LeaveCriticalSection前兩個API只是分別圍繞NTDLL API RtlInitializeCriticalSection和RtlInitializeCriticalSectionAndSpinCount的瘦包裝。所有剩余例程都被提交給NTDLL中的函數(shù)。另外,對RtlInitializeC
36、riticalSection的調(diào)用是另一個圍繞RtlInitializeCriticalSectionAndSpinCount調(diào)用的瘦包裝,其旋轉(zhuǎn)數(shù)的值為0。使用臨界區(qū)的時候?qū)嶋H上是在幕后使用以下NTDLL API:RtlInitializeCriticalSectionAndSpinCount RtlEnterCriticalSection RtlTryEnterCriticalSection RtlLeaveCriticalSection RtlDeleteCriticalSection在這一討論中,我們采用Kernel32名稱,因為大多數(shù)Win32程序員對它們更為熟悉。Initializ
37、eCriticalSectionAndSpinCount對臨界區(qū)的初始化非常簡單。RTL_CRITICAL_SECTION結(jié)構(gòu)中的字段被賦予其起始值。與此類似,分配RTL_CRITICAL_SECTION_DEBUG結(jié)構(gòu)并對其進(jìn)行初始化,將RtlLogStackBackTraces調(diào)用中的返回值賦予CreatorBackTraceIndex,并建立到前面臨界區(qū)的鏈接。順便說一聲,CreatorBackTraceIndex一般接收到的值為0。但是,如果有Gflags和Umdh實用工具,可以輸入以下命令:Gflags/i MyProgram.exe+ust Gflags/i MyProgram.e
38、xe/tracedb 24這些命令使得MyProgram的Image File Execution Options下添加了注冊表項。在下一次執(zhí)行MyProgram時會看到此字段接收到一個非0數(shù)值。有關(guān)更多信息,參閱知識庫文章Q 268343Umdhtools.exe:How to Use Umdh.exe to Find Memory Leaks。臨界區(qū)初始化中另一個需要注意的問題是:前64個RTL_CRITICAL_SECTION_DEBUG結(jié)構(gòu)不是由進(jìn)程堆中分配,而是來自位于NTDLL內(nèi)的.data節(jié)的一個數(shù)組。在完成臨界區(qū)的使用之后,對DeleteCriticalSection(其命名不
39、當(dāng),因為它只刪除RTL_CRITICAL_SECTION_ DEBUG)的調(diào)用遍歷一個同樣可理解的路徑。如果由于線程在嘗試獲得臨界區(qū)時被阻止而創(chuàng)建了一個事件,將通過調(diào)用ZwClose來銷毀該事件。接下來,在通過RtlCriticalSectionLock獲得保護(hù)之后(NTDLL以一個臨界區(qū)保護(hù)它自己的內(nèi)部臨界區(qū)列表-您猜對了),將調(diào)試信息從鏈中清除,對該臨界區(qū)鏈表進(jìn)行更新,以反映對該信息的清除操作。該內(nèi)存由空值填充,并且如果其存儲區(qū)是由進(jìn)程堆中獲得,則調(diào)用RtlFreeHeap將使得其內(nèi)存被釋放。最后,以零填充RTL_CRITICAL_SECTION。有兩個API要獲得受臨界區(qū)保護(hù)的資源-Tr
40、yEnterCriticalSection和EnterCriticalSection。如果一個線程需要進(jìn)入一個臨界區(qū),但在等待被阻止資源變?yōu)榭捎玫耐瑫r,可執(zhí)行有用的工作,那么TryEnterCriticalSection正是您需要的API。此例程測試此臨界區(qū)是否可用;如果該臨界區(qū)被占用,該代碼將返回值FALSE,為該線程提供繼續(xù)執(zhí)行另一任務(wù)的機(jī)會。否則,其作用只是相當(dāng)于EnterCriticalSection。如果該線程在繼續(xù)進(jìn)行之前確實需要擁有該資源,則使用EnterCriticalSection。此時,取消用于多處理器計算機(jī)的SpinCount測試。這一例程與TryEnterCritica
41、lSection類似,無論該臨界區(qū)是空閑的或已經(jīng)被該線程所擁有,都調(diào)整對該臨界區(qū)的簿記。注意,最重要的LockCount遞增是由x86lock前綴完成的,這一點非常重要。這確保了在某一時間內(nèi)只有一個CPU可以修改該LockCount字段。(事實上,Win32 InterlockedIncrement API只是一個具有相同鎖定前綴的ADD指令。)如果調(diào)用線程無法立即獲得該臨界區(qū),則調(diào)用RtlpWaitForCriticalSection將該線程置于等待狀態(tài)。在多處理器系統(tǒng)中,EnterCriticalSection旋轉(zhuǎn)SpinCount所指定的次數(shù),并在每次循環(huán)訪問中測試該臨界區(qū)的可用性。如果
42、此臨界區(qū)在循環(huán)期間變?yōu)榭臻e,該線程獲得該臨界區(qū),并繼續(xù)執(zhí)行。RtlpWaitForCriticalSection可能是這里所給的所有過程中最為復(fù)雜、最為重要的一個。這并不值得大驚小怪,因為如果存在一個死鎖并涉及臨界區(qū),則利用調(diào)試器進(jìn)入該進(jìn)程就可能顯示出RtlpWaitForCriticalSection內(nèi)ZwWaitForSingleObject調(diào)用中的至少一個線程。如偽碼中所顯示,在RtlpWaitForCriticalSection中有一點簿記工作,如遞增EntryCount和ContentionCount字段。但更重要的是:發(fā)出對LockSemaphore的等待,以及對等待結(jié)果的處理。默
43、認(rèn)情況是將一個空指針作為第三個參數(shù)傳遞給ZwWaitForSingleObject調(diào)用,請求該等待永遠(yuǎn)不要超時。如果允許超時,將生成調(diào)試消息字符串,并再次開始等待。如果不能從等待中成功返回,就會產(chǎn)生中止該進(jìn)程的錯誤。最后,在從ZwWaitForSingleObject調(diào)用中成功返回時,則執(zhí)行從RtlpWaitForCriticalSection返回,該線程現(xiàn)在擁有該臨界區(qū)。RtlpWaitForCriticalSection必須認(rèn)識到的一個臨界條件是該進(jìn)程正在被關(guān)閉,并且正在等待加載程序鎖定(LdrpLoaderLock)臨界區(qū)。RtlpWaitForCriticalSection一定不能允許
44、該線程被阻止,但是必須跳過該等待,并允許繼續(xù)進(jìn)行關(guān)閉操作。LeaveCriticalSection不像EnterCriticalSection那樣復(fù)雜。如果在遞減RecursionCount之后,結(jié)果不為0(意味著該線程仍然擁有該臨界區(qū)),則該例程將以ERROR_SUCCESS狀態(tài)返回。這就是為什么需要用適當(dāng)數(shù)目的Leave調(diào)用來平衡Enter調(diào)用。如果該計數(shù)為0,則OwningThread字段被清零,LockCount被遞減。如果還有其他線程在等待,例如LockCount大于或等于0,則調(diào)用RtlpUnWaitCriticalSection。此幫助器例程創(chuàng)建LockSemaphore(如果其尚未存在),并發(fā)出該信號提醒操作系統(tǒng):該線程已經(jīng)釋放該臨界區(qū)。作為通知的一部分,等待線程之一退出等待狀態(tài),為運(yùn)行做好準(zhǔn)備。最后要說明的一點是,MyCriticalSections程序如何確定臨界區(qū)鏈的起始呢?如果有權(quán)訪問NTDLL的正確調(diào)試符號,則對該列表的查找和遍歷非常簡單。首先,定位符號RtlCriticalSectionList,清空其內(nèi)容(它指向第一個RTL_CRITICAL_SECTION_DEBUG結(jié)構(gòu)),并開始遍歷。但是,并不是所有的系統(tǒng)都有調(diào)試符號,RtlCriticalSectionLi
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負(fù)責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 二零二五年度教育培訓(xùn)機(jī)構(gòu)教育行業(yè)數(shù)據(jù)服務(wù)協(xié)議
- 二零二五年度農(nóng)業(yè)科技文職人員聘用協(xié)議
- 2025年度茶樓合作經(jīng)營協(xié)議書:茶樓與茶藝茶具研發(fā)中心的合作合同
- 二零二五年度知識產(chǎn)權(quán)質(zhì)押合同解除與資金返還協(xié)議
- 2025年度船舶租賃與船舶技術(shù)咨詢服務(wù)協(xié)議
- 2025年度超市轉(zhuǎn)讓與智能化升級改造合作協(xié)議
- 2025年度智能化社區(qū)物業(yè)委托經(jīng)營管理合同
- 專業(yè)資格教育培訓(xùn)合作協(xié)議
- 新型儲能技術(shù)應(yīng)用開發(fā)合作協(xié)議
- 行路難:古典詩詞中的壯志情懷教案
- 《中國潰瘍性結(jié)腸炎診治指南(2023年)》解讀
- 中國故事英文版哪吒英文二篇
- 鐵路橋梁工程各工序工效分析
- 2022《文創(chuàng)產(chǎn)品的設(shè)計》PPT演示文稿
- 介入科制度匯編
- 鍍鋅管理論重量表常用
- 鐵總計統(tǒng)【2017】177號關(guān)于進(jìn)一步加強(qiáng)鐵路建設(shè)項目征地拆遷工作和費(fèi)用管理的指導(dǎo)意見
- 電子技術(shù)基礎(chǔ)與技能-(3)
- 部編版四年級下冊語文第二單元課文教材分析及全部教案
- 美術(shù)開學(xué)第一課(課堂PPT)
- 出生證明委托書(共2頁)
評論
0/150
提交評論