信号灯对象

任何驱动程序都可以使用信号灯对象在其驱动程序创建的线程和其他驱动程序例程之间同步作。 例如,驱动程序专用线程可能会在驱动程序没有未完成的 I/O 请求时进入等待状态,并且驱动程序的调度例程可能会在它们排队 IRP 后将信号灯设置为信号状态。

在请求 I/O作的线程上下文中运行的最高级别驱动程序的调度例程可能使用信号灯来保护调度例程之间共享的资源。 用于同步 I/O作的较低级别的驱动程序调度例程也可能使用信号灯来保护在该调度例程子集之间共享的资源,或者与驱动程序创建的线程共享。

使用信号灯对象的任何驱动程序必须在等待或释放信号灯之前调用 KeInitializeSemaphore。 下图说明了具有线程的驱动程序如何使用信号灯对象。

图,说明正在等待信号灯对象。

如上图所示,此类驱动程序必须提供信号灯对象的存储,该对象应驻留。 驱动程序可以使用驱动程序创建的设备对象的 设备扩展、控制器扩展(如果使用 控制器对象或驱动程序分配的非分页池)。

当驱动程序的 AddDevice 例程调用 KeInitializeSemaphore时,它必须传递指向信号灯对象的驱动程序常驻存储的指针。 此外,调用方必须为信号量对象指定 计数,如上图所示,确定其初始状态(Signaled 为非零)。

调用方还必须为信号灯指定 限制,可以是以下任一情况:

  • 限制 = 1

    当此信号灯设置为 Signaled 状态时,等待信号灯设置为信号灯的单个线程将有资格执行,并且可以访问信号灯保护的任何资源。

    这种类型的信号灯也称为 二进制信号灯,因为线程具有或无权访问受信号灯保护的资源。

  • 限制 > 1

    当此信号灯设置为 Signaled 状态时,等待信号灯对象设置为信号灯状态的一些线程将有资格执行,并且可以访问信号灯保护的任何资源。

    这种类型的信号灯称为 计数信号灯,因为将信号灯设置为 Signaled 状态的例程还指定了多少个等待线程可以将状态从等待更改为就绪。 当信号灯初始化时,此类等待线程数可以是 限制 设置,或者小于此预设数 限制

很少有设备或中间驱动程序具有单个驱动程序创建的线程;更少的线程可能等待获取或释放信号灯的一组线程。 很少有系统提供的驱动程序使用信号灯对象,而那些驱动程序则使用二进制信号灯更少。 尽管二进制信号灯在功能上可能与 互斥对象类似,但二进制信号灯不提供针对在 SMP 计算机中运行的系统线程的死锁的内置保护。

加载具有初始化信号灯的驱动程序后,它可以同步保护共享资源的信号灯上的作。 例如,具有管理 IRP 队列的设备专用线程(例如系统软盘控制器驱动程序)的驱动程序可能会同步信号灯上的 IRP 队列,如下图所示:

  1. 线程调用 KeWaitForSingleObject,并指向初始化信号灯对象的驱动程序提供的存储的指针,以将自身置于等待状态。

  2. IRP 开始出现需要设备 I/O作。 驱动程序的调度例程在旋转锁控制下将每个此类 IRP 插入联锁队列,并使用指向信号量对象的指针调用 KeReleaseSemaphore, 线程的驱动程序确定优先级提升(递增,如上图所示),调整 1 添加到信号灯计数,因为每个 IRP 已排队, 和一个布尔 Wait 设置为 FALSE。 非零信号灯计数将信号灯对象设置为 Signaled 状态,从而将等待线程的状态更改为就绪。

  3. 内核在处理器可用后立即调度执行线程:也就是说,没有其他优先级较高的线程当前处于就绪状态,并且没有内核模式例程在更高的 IRQL 上运行。

    线程从旋转锁控制下的互锁队列中删除 IRP,将其传递给其他驱动程序例程以供进一步处理,并再次调用 KeWaitForSingleObject。 如果信号灯仍设置为信号状态(即,其计数仍为非零,表示驱动程序的锁队列中存在更多 IRP),内核将再次更改线程的状态,使其状态从等待变为就绪状态。

    通过以这种方式使用计数信号灯,这样一个驱动程序线程就会“知道”,每当该线程运行时,都会从互锁队列中删除 IRP。

有关在调用 KeReleaseSemaphore时管理 IRQL 的信息,请参阅 KeReleaseSemaphore的“备注”部分。

在大于 PASSIVE_LEVEL 的 IRQL 上运行的任何标准驱动程序例程都无法等待任何调度程序对象的非零间隔,而不会降低系统;有关详细信息,请参阅 内核调度程序对象。 但是,此类例程可以在 IRQL 上运行小于或等于DISPATCH_LEVEL时调用 KeReleaseSemaphore

有关运行标准驱动程序例程的 IRQL 的摘要,请参阅 管理硬件优先级。 有关特定支持例程的 IRQL 要求,请参阅例程的参考页。