信号灯对象
任何驱动程序都可以使用信号量对象在其驱动程序创建的线程和其他驱动程序例程之间同步操作。 例如,当驱动程序没有未完成的 I/O 请求时,驱动程序专用线程可能会将自身置于等待状态,并且驱动程序的调度例程可能会在将 IRP 排入队列后将信号量设置为“已信号”状态。
在请求 I/O 操作的线程上下文中运行的最高级别驱动程序的调度例程可能使用信号灯来保护调度例程之间共享的资源。 用于同步 I/O 操作的较低级别的驱动程序调度例程也可能使用信号灯来保护该调度例程子集之间共享的资源或与驱动程序创建的线程共享的资源。
任何使用信号灯对象的驱动程序都必须在等待或释放信号量之前调用 KeInitializeSemaphore 。 下图演示了具有线程的驱动程序如何使用信号灯对象。
如上图所示,此类驱动程序必须为信号灯对象提供存储,该信号量应驻留。 驱动程序可以使用驱动程序创建 的设备对象的设备扩展 、控制器扩展(如果它使用 控制器对象)或驱动程序分配的非分页池。
当驱动程序的 AddDevice 例程调用 KeInitializeSemaphore 时,它必须将指针传递给驱动程序的信号灯对象的常驻存储。 此外,调用方必须为信号量对象指定 Count ,如上图所示,该对象确定信号量) 的初始状态 (非零。
调用方还必须指定信号灯 的 Limit ,可以是以下任一项:
限制 = 1
当此信号灯设置为“已信号”状态时,等待信号灯设置为“已信号”状态的单个线程将有资格执行,并且可以访问信号量保护的任何资源。
这种类型的信号灯也称为 二进制信号灯 ,因为线程对信号灯保护的资源具有或没有独占访问权限。
限制 > 1
当此信号灯设置为“已信号”状态时,等待信号量对象设置为“已信号”状态的一些线程将有资格执行,并且可以访问信号量保护的任何资源。
这种类型的信号量称为 计数信号灯 ,因为将信号量设置为“已信号”状态的例程还指定有多少等待线程可以将其状态从等待更改为“就绪”。 此类等待线程的数量可以是初始化信号灯时设置的 “限制 ”,也可以是低于此预设 限制的某个数量。
很少有设备或中间驱动程序具有单个驱动程序创建的线程;有一组线程可能等待信号灯被获取或释放,甚至更少。 系统提供的驱动程序很少使用信号灯对象,而在这些驱动程序中,使用二元信号灯的驱动程序甚至更少。 尽管二进制信号灯在功能上可能类似于 互斥对象,但二进制信号灯不提供针对 SMP 计算机中运行的系统线程的互斥对象具有的死锁的内置保护。
加载具有已初始化信号灯的驱动程序后,它可以同步保护共享资源的信号量上的操作。 例如,具有管理 IRP 排队的设备专用线程的驱动程序(例如系统软盘控制器驱动程序)可能会在信号灯上同步 IRP 队列,如上图所示:
线程调用 KeWaitForSingleObject ,其中包含指向初始化信号灯对象的驱动程序提供的存储的指针,使自身处于等待状态。
需要设备 I/O 操作的 IRP 开始出现。 驱动程序的调度例程将每个此类 IRP 插入到旋转锁定控制下的互锁队列中,并使用指向信号量对象的指针调用 KeReleaseSemaphore,这是驱动程序确定的线程优先级提升 (增量,如上图) 所示,调整为 1,在每个 IRP 排队时添加到信号灯的 Count 中, 并将布尔等待设置为 FALSE。 非零信号灯计数将信号量对象设置为“已信号”状态,从而将等待线程的状态更改为“就绪”。
当处理器可用时,内核会立即调度线程以执行:也就是说,当前没有其他具有更高优先级的线程处于就绪状态,并且没有内核模式例程可在更高的 IRQL 上运行。
线程在旋转锁定控制下从互锁队列中删除 IRP,将其传递给其他驱动程序例程以供进一步处理,并再次调用 KeWaitForSingleObject 。 如果信号灯仍设置为信号状态 (即,其计数将保持非零值,表示驱动程序的联锁队列) 中存在更多 IRP,内核再次将线程的状态从“等待”更改为“就绪”。
通过以这种方式使用计数信号量,此类驱动程序线程“知道”每当该线程运行时,都会从互锁队列中删除 IRP。
有关调用 KeReleaseSemaphore 时管理 IRQL 的特定信息,请参阅 KeReleaseSemaphore 的“备注”部分。
在大于 PASSIVE_LEVEL 的 IRQL 上运行的任何标准驱动程序例程都不能等待任何调度程序对象的非零间隔,而不会关闭系统;有关详细信息 ,请参阅内核调度程序对象 。 但是,此类例程可以在 IRQL 小于或等于 DISPATCH_LEVEL 的情况下调用 KeReleaseSemaphore 。
有关运行标准驱动程序例程的 IRQL 的摘要,请参阅 管理硬件优先级。 有关特定支持例程的 IRQL 要求,请参阅例程的参考页。