定义和使用事件对象
任何使用事件对象的驱动程序都必须调用 KeInitializeEvent、 IoCreateNotificationEvent 或 IoCreateSynchronizationEvent ,然后才能等待、设置、清除或重置事件。 下图演示了具有线程的驱动程序如何使用事件对象进行同步。
如上图所示,此类驱动程序必须为事件对象提供存储,该事件对象必须是驻留的。 驱动程序可以使用驱动程序创建 的设备对象的设备扩展 、控制器扩展(如果它使用 控制器对象)或驱动程序分配的非分页池。
当驱动程序调用 KeInitializeEvent 时,它必须传递指向事件对象的驱动程序常驻存储的指针。 此外,调用方必须为事件对象指定初始状态 (已发出信号或未发出信号) 。 调用方还必须指定事件类型,可以是以下任一类型:
SynchronizationEvent
当 同步事件 设置为“已信号”状态时,等待事件重置为Not-Signaled的单个线程将有资格执行,并且事件的状态会自动重置为“未发出信号”。
这种类型的事件有时称为 自动清理事件,因为每次满足等待时,其信号状态都会自动重置。
NotificationEvent
当通知事件设置为“已信号”状态时,等待事件重置为Not-Signaled的所有线程都将符合执行条件,并且事件将保持为“已信号”状态,直到发生显式重置Not-Signaled:也就是说,使用给定的事件指针调用 KeClearEvent 或 KeResetEvent。
很少有设备或中间驱动程序具有单个驱动程序专用线程,更不用说一组线程,这些线程可能会通过等待保护共享资源的事件来同步其操作。
大多数使用事件对象等待 I/O 操作完成的驱动程序在调用 KeInitializeEvent 时将输入类型设置为 NotificationEvent。 为驱动程序使用 IoBuildSynchronousFsdRequest 或 IoBuildDeviceIoControlRequest 创建的 IRP 设置的事件对象几乎总是初始化为 NotificationEvent, 因为调用方将等待事件的通知,指示其请求已被一个或多个较低级别的驱动程序满足。
驱动程序初始化自身后,其驱动程序专用线程(如果有)和其他例程可以同步其在事件上的操作。 例如,具有管理 IRP 排队的线程的驱动程序(例如系统软盘控制器驱动程序)可能会同步事件上的 IRP 处理,如上图所示:
线程已取消排队以在设备上处理 IRP,它使用指向初始化事件对象的驱动程序提供的存储的指针调用 KeWaitForSingleObject 。
其他驱动程序例程执行满足 IRP 所需的 I/O 操作,当这些操作完成时,驱动程序的 DpcForIsr 例程使用指向事件对象的指针调用 KeSetEvent ,驱动程序确定的线程优先级提升 (增量,如上图) 所示,布尔 等待 设置为 FALSE。 调用 KeSetEvent 会将事件对象设置为“已信号”状态,从而将等待线程的状态更改为“就绪”。
当处理器可用时,内核会立即调度线程以执行:也就是说,当前没有其他具有更高优先级的线程处于就绪状态,并且没有内核模式例程可在更高的 IRQL 上运行。
如果 DpcForIsr 尚未使用 IRP 调用 IoCompleteRequest ,线程现在可以完成 IRP,并且可以取消排队以在设备上处理的另一个 IRP。
调用将 Wait 参数设置为 TRUE 的 KeSetEvent 表示调用方打算在从 KeSetEvent 返回时立即调用 KeWaitForSingleObject 或 KeWaitForMultipleObjects 支持例程。
请考虑以下有关将Wait参数设置为KeSetEvent 的准则:
在 IRQL < DISPATCH_LEVEL运行的可分页线程或可分页驱动程序例程不应调用将 Wait 参数设置为 TRUE 的 KeSetEvent。 如果调用方碰巧在调用 KeSetEvent 和 KeWaitForSingleObject 或KeWaitForMultipleObjects 之间分页,则此类调用会导致严重页面错误。
在 IRQL = DISPATCH_LEVEL 下运行的任何标准驱动程序例程都不能等待任何调度程序对象的非零间隔,而不会关闭系统。 但是,此类例程可以在 IRQL 小于或等于 DISPATCH_LEVEL 运行时调用 KeSetEvent 。
有关运行标准驱动程序例程的 IRQL 的摘要,请参阅 管理硬件优先级。
KeResetEvent 返回给定 事件的先前状态:调用 KeResetEvent 时,它是否设置为 Signaled。 KeClearEvent 只是将给定 事件 的状态设置为“未发出信号”。
对于何时调用上述支持例程,请考虑以下准则:
为了提高性能,每个驱动程序都应调用 KeClearEvent ,除非调用方需要 KeResetEvent 返回的信息来确定接下来要执行的操作。