WDK tips (9.2) 同步机制与锁 (2)

就跟上回讲的一样,动不动就使用spin lock是非常不合适的行为,我们应该尽量使用别的同步机制。NT内核提供了一族统称为dispatcher lock的锁,它们各有各的特点,适应不同的应用场景,了解它们的特性可以帮助你找到最适合自己的同步机制,避免spin lock的滥用。 表征dispatcher loco的数据结构拥有一个公共的头叫做DISPATCHER_HEADER,凡是有这个结构的锁都可以通过KeWaitForSingleObject(或者KeWaitForMultipleObjects)进入临界区。NT内核诞生的那段时间正好是面向对象概念大行其道的时候,所以NT的设计也融入了很多面向对象的思想,比如DISPATCHER_HEADER这个数据结构就可以看成是KEVENT,KMUTEX等等一系列数据结构的父类,而KeWaitForSingleObject则可以看成是父类中的public方法,可以被子类继承。DISPATCHER_HEADER的定义如下:

 1 typedef struct _DISPATCHER_HEADER {
 2     union {
 3         struct {
 4             UCHAR Type;
 5             union {
 6                 UCHAR Absolute;
 7                 UCHAR NpxIrql;
 8 
 9             };
10             union {
11                 UCHAR Size;
12                 UCHAR Hand;
13             };
14             union {
15                 UCHAR Inserted;
16                 BOOLEAN DebugActive;
17             };
18         };
19         volatile LONG Lock;
20     };
21     LONG SignalState;
22     LIST_ENTRY WaitListHead;
23 } DISPATCHER_HEADER; 

 

我们主要关心SignalState和WaitListHead这两个域。SignalState表征该锁是否处于被占用状态,WaitListHead是一个列表,将所有等在该锁上的线程全部记录下来,等锁被释放后就从这个列表中挑选一个(或者多个)线程唤醒。它与线程的关系大致如下:

45a2fb7d2e31d45f28388a8a

以下是几个典型的dispatcher lock:

所有dispatcher loch中最简单的是event,它只包含了DISPATCHER_HEADER数据结构,没有任何额外内容。一个线程可以用KeWaitFOrSIngleObject获得锁(使SignalState处于un-signal状态),并用KeSetEvent释放(使SignalState处于signal状态)。这里有个小问题:由于DISPATCHER_HEADER中并没有记录当前获得锁的线程是哪一个,所以一旦使用event发生死锁后,你想用windbg之类的工具分析到底谁占用了锁是不可能的,相比之下mutex等就可以。并且由于没有owner thread信息我们也没办法知道一个线程到底get了几次这个锁,所以如果同一线程获取两次并且只释放一次,那么锁会处于signal状态,你写代码的时候可要小心这一点。我不知道这是设计失误还是怎样,DISPATCHER_HEADER中已经有那么多成员了,多一个owner thread信息就会差很多吗,不知道MS怎么想的。

Mutex与évent很相似,不过ta它加了一些额外信息来记录当前获得锁的线程的信息。如果同一个线程获得mutex两次,那么里面有一个count就会被置为2,除非你release mutex两次,否则别的线程无法获得该mutex。另外作为一个副作用,当线程获得mutex后kernel APC会被禁用,所以当你的程序里同时有timer等APC机制和mutex时得多加注意。另外,释放锁的线程与获得锁的线程必须是同一个,否则就会BSOD。

Semaphore的出现为了解决这样的问题:有的资源数量并不是只有一个,它允许并且只允许(不同的)线程加锁特定次数,当资源没有用尽时,KeWaitFOrSIngleObject立即返回,到上限后则是进入等待状态。为了做到这一点Semaphore需要增加两个域来管理信息:limitation域表征资源上限,count域表征当前用掉了多少。当我们调用KeWaitFOrSingleObject时count会减1,如果count>-limitation则进入un-signal状态;调用KeReleaseSemaphore时count加1,如果count >= 0 则进入signal状态。

另外还有一些dispatcher lock没有列出,各位可以到MSDN上查阅。这些锁都大同小异,最大的不同在于决定何时进入un-signal状态何时进入signal状态的规则,这里有张表列出了大部分dispatcher lock的规则:

锁类型 signal状态规则 影响到的线程
Mutex Release Mutex之后 唤醒一条线程
Semaphore count==0时 唤醒所有的线程
同步类型event Set Event之后 唤醒一条线程
通知类型event Set Event之后 唤醒所有线程
Notification Timer 时间到 唤醒所有线程
Process 进程被销毁后 唤醒所有线程
Thread 线程被销毁后 唤醒所有线程
File IO complete 唤醒所有线程

以上就是dispatcher lock的大致内容,下次我们继续讲resource和executive lock这两种特别的锁。

posted @ 2012-12-30 19:20  gussing  阅读(2245)  评论(2编辑  收藏  举报