本次我們來聊兩個不常見的鎖類型:Resource與Fast Mutexes。這兩種鎖只有在內核態可用,並且微軟的design guide里也並未提及,但它們在有些場景下卻非常好用。我們學操作系統或者數據結構的時候一定接觸過一種鎖類型叫做讀寫鎖,在讀寫鎖的保護下,一個資源可以被很多線程讀取,卻只能被一個線程寫。如果你有針對多線程環境好好考慮過你的設計,那么這種單線程寫多線程讀的模型多半已經很熟悉了。讀寫鎖特別適合這種場景,Resource便是內核態的讀寫鎖。而Fast Mutexes的出現主要為了解決性能問題,我們上回說過Mutex內部會保留一個count域,同一個線程獲取了多少次鎖,它就必須釋放多少次。Fast Mutex去除了這個限制,因為它假設程序員不會在同一個線程里多次獲取鎖(如果有那就是程序員自己的責任了);另外Fast Mutex也不會禁用APC,但它內部有機制保證APC不能訪問被它保護着的代碼片段,這是Fast Mutex與event最大的區別了。
Recource
Resource 由ExInitializeResourceLite函數負責初始化,用完后需要調用ExDeleteResourceLite 負責回收資源。以只讀方式獲取鎖的函數一共有三個,分別是ExAcquireResourceSharedLite,ExAcquireSharedWaitForExclusive與ExAcquireSharedStarveExclusive;以可寫方式獲取鎖的函數只有一個為ExAcquireResourceExclusiveLite。這幾個函數都有一個Wait參數表征無法獲得鎖的情況下是等待還是立即返回。ExConvertExclusiveToSharedLite函數可以改變鎖的類型,將可寫狀態改為只讀狀態。釋放鎖的函數不區分只讀還是可寫,都是ExReleaseResourceLite或者ExReleaseResourceForThreadLite。
下面是獲取鎖的各函數返回成功的條件:
ExAcquireResourceSharedLite
- Rescource沒被任何線程占用
- Resource已經被當前線程占用。如果之前是以可寫方式占用的,繼續保持可寫。
- Resource被其他線程以只讀方式占用,並且沒有其他線程正在嘗試用可寫方式占用。
- 如果前幾條規則都不滿足,該函數一般會進入等待狀態,除非Wait參數是FALSE
ExAcquireSharedWaitForExclusive
- Resource沒有被任何線程占用
- Resource被當前線程以可寫方式占用,調用后占用方式依然是可寫
- Resource被當前線程以只讀方式占用,並且沒有其他線程正在嘗試用可寫方式占用。調用后占用方式依然為只讀
- 如果前幾條規則都不滿足,該函數一般會進入等待狀態,除非Wait參數是FALSE
- 與ExAcquireResourceSharedLite最大的不同在於如果當前線程已經占有並且有其他線程等待寫入,ExAcquireResourceSharedLite依然可以成功但ExAcquireSharedWaitForExclusive會失敗
ExAcquireSharedStarveExclusive
- Resource沒有被任何線程占用
- Resource已經被當前線程占用。如果之前是以可寫方式占用的,繼續保持可寫。
- Resource被其他線程以只讀方式占用,即使有其他線程正在嘗試用可寫方式占用。
- 如果前幾條規則都不滿足,該函數一般會進入等待狀態,除非Wait參數是FALSE
- 與ExAcquireResourceSharedLite最大的不同在於如果其他線程已經以只讀方式占有並且有其他線程等待寫入,ExAcquireResourceSharedLite會失敗但ExAcquireSharedStarveExclusive依然可以成功。
ExAcquireResourceExclusiveLite
- Resource沒有被任何線程占用
- Resource已被當前線程用可寫方式占有。如果當前線程已經用只讀方式占有,函數調用會失敗
- 如果其他線程已經占有了resource,不管是只讀還是可寫,都需要等待,除非Wait參數為FALSE
Resource不能在DISPATCH_LEVEL及之上的IRQL中使用。
Fast Mutex
Fast Mutex使用ExInitializeFastMutex函數初始化,沒有相應的銷毀函數。獲取鎖的函數有兩個:ExAcquireFastMutex在IRQL<APC_LEVEL時使用;ExAcquireFastMutexUnsafe在IRQL=APC_LEVEL時使用。相應的釋放函數為ExReleaseFastMutex與ExReleaseFastMutexUnsafe,必須配套。ExTryToAcquireFastMutex嘗試獲得鎖,如果獲取成功返回TRUE,失敗則立刻返回FALSE。一般來講ExAcquireFastMutex會自動將IRQL升到APC_LEVEL,如果你在ExAcquireFastMutex之后強行降低IRQL,那么一定要記得在調用ExReleaseFastMutex之前把IRQL升回到APC_LEVEL。
Resource不能在DISPATCH_LEVEL及之上的IRQL中使用。
鎖與IRQL
各種鎖使用時的IRQL規定都不同,下面這張表列出了各種鎖在獲取以及釋放時的IRQL要求。“嘗試獲得”也就是我們說的TryLockXXX函數,它們在獲取鎖失敗時會馬上返回。
鎖類型 |
嘗試獲得 |
獲得 | 釋放 |
普通spin lock |
<= DISPATCH_LEVEL |
DISPATCH_LEVEL |
|
普通ISR spin lock |
<= DIRQL |
DIRQL |
|
同步ISR spin lock |
<= 指定DIRQL |
指定DIRQL |
|
Mutex |
<=DISPATCH_LEVEL |
<DISPATCH_LEVEL |
<=DISPATCH_LEVEL |
Semaphore |
<=DISPATCH_LEVEL |
<DISPATCH_LEVEL |
<=DISPATCH_LEVEL |
同步Event |
<=DISPATCH_LEVEL |
<DISPATCH_LEVEL |
<=DISPATCH_LEVEL |
通知Event |
<=DISPATCH_LEVEL |
<DISPATCH_LEVEL |
<=DISPATCH_LEVEL |
同步Timer |
<=DISPATCH_LEVEL |
<DISPATCH_LEVEL |
- |
通知Timer |
<=DISPATCH_LEVEL |
<DISPATCH_LEVEL |
- |
進程 |
<=DISPATCH_LEVEL |
<DISPATCH_LEVEL |
- |
線程 |
<=DISPATCH_LEVEL |
<DISPATCH_LEVEL |
- |
文件 |
<=DISPATCH_LEVEL |
<DISPATCH_LEVEL |
- |
Resources |
< DISPATCH_LEVEL |
<DISPATCH_LEVEL |
<=DISPATCH_LEVEL |