阴沟里翻船之KeSetEvent

KeSetEvent是个使用频率很高的内核支持函数,但经常使用未必意味着确实了解它。上周就曾遇到一件怪事,系统线程在调用KeSetEvent后线程IRQL竟然从PASSIVE_LEVEL提升至DISPATCH_LEVEL,以至后续的操作出错:Bug Check 0xA: IRQL_NOT_LESS_OR_EQUAL。

先看看它的函数声明:

LONG
KeSetEvent(
    IN PRKEVENT Event,
    IN KPRIORITY Increment,
    IN BOOLEAN Wait
);

比较简单,一共只有3个参数:

  • Event: 准备被激活的事件
  • Increment: 预备给被唤醒线程临时提升线程优先级的增量。一般情况下均为0,但针对不同的子系统及对响应快慢的不同要求,会有不同的取值。如网络相关:IO_NETWORK_INCREMENT=2;键盘、鼠标涉及用户界面交互部分:取值为6 (IO_KEYBOARD_INCREMENT, IO_MOUSE_INCREMENT);声音相关,IO_SOUND_INCREMENT=8
  • Wait: 这个参数名字起的太不妥贴,非常容易误导。我就曾以为将此参数设为TRUE表示KeSetEvent将等待睡眠在此Event上的线程被唤醒,直到KeWaitForXXXObject返回。现在看来这种自以为是的默认是全然没有根据的。先看DDK中怎么说这个参数吧:
  • Specifies whether the call to KeSetEvent is to be followed immediately by a call to one of the KeWaitXxx routines. If TRUE, the KeSetEvent call must be followed by a call to KeWaitForMultipleObjects, KeWaitForMutexObject, or KeWaitForSingleObject.

    由此来看,此参数就是为了避免不必要的锁开销及可能的线程调度开销。你可能会认为,调用者先调用KeSetEvent(Event, 0, TRUE),然后直接跟上了KeWaitForXXXObject(Event …),那岂不是多此一举?控制权本就在自己手中,何必再搞个假放权真夺权的无用功呢?再说即便是有用功,也有点任人唯亲或肥水不流外人田的嫌疑吧?

    但反过来想想,如果KeSetEvent和KeWaitForXXXObject所操作的Event对象不是同一个的话,那所有的疑惑便都得到解答了:放的是权A,要夺的则是权B ,但无论如何,作为特权一层,还是可以享受些优待的,至少进入权B争夺之门时不用再去排队了。至于什么时间能得到权B,那是调度器说了才算的,你或许不由得去想,若去公关调度器话,那后门该如何走呢?!

    至此,就Wait参数的使用上,应该不会再有”To be or not to be, that’s a question”的疑惑了吧?

说了这么多,但对KeSetEvent为什么会导致IRQL的提升还没有解答,但其实答案已经明了:KeSetEvent执行时首先会调用KiLockDispatcherDatabase()来提升IRQL以阻止调度器的调度,在Wait = TRUE的情况下,返回时KeSetEvent会保持着高IRQL,而降低IRQL的工作将由后续的KeWaitForXXXObject来做。考虑到两个函数所操作的Event对象并不一定是同一个,所以二者都要通线程结构(KTHREAD)来进行通讯。

最后再说说返回值吧,DDK中是这样说的:If the previous state of the event object was signaled, a nonzero value is returned. 这句话说得还要让你玩“我猜猜”,其隐含的意思就是说如果此Event先前没有被激活,则返回0值。所以,简言之,KeSetEvent的返回值就是此Event先前的状态:Signaled or Not Signaled !

2 条评论

  1. 还在创业中感到迷茫的你,看了以上这27条干货,是不是也感到豁然开朗了?正如这么创业者自己所言,他从打工仔蜕变为公司老板,最终因为对趋势把握问题,重新回到原点。可见对于任何一个企业家而言,对于未来整体趋势把控是十分重要的。试想如果一名企业家不能深谋远虑,比别人看得更长远,又怎么能带领自己的企业和员工迈向更高的台阶呢。

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注