WDK tips (9.3) 同步机制与锁 (3)
本次我们来聊两个不常见的锁类型: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 |