使用自旋锁:示例
尽量减少驱动程序持有旋转锁的时间可以显著提高驱动程序的性能和整个系统的性能。 例如,以下图为例,该图显示了中断旋转锁如何保护必须在 ISR 与 SMP 计算机中的 StartIo 和 DpcForIsr 例程之间共享的设备特定数据。
驱动程序的 ISR 在一个处理器上的 DIRQL 上运行时,其 StartIo 例程在第二个处理器上以DISPATCH_LEVEL运行。 内核中断处理程序保存驱动程序的 ISR 的 InterruptSpinLock,该 ISR 访问特定于设备的受保护数据,如状态或指向驱动程序设备扩展中同步Context) 的设备 (寄存器的状态或指针。 StartIo 例程(已准备好访问 SynchronizeContext)调用 KeSynchronizeExecution,将指针传递到上图中关联的中断对象、共享的 SynchronizeContext 和驱动程序的 SynchCritSection 例程 (AccessDevice) 。
在 ISR 返回,从而释放驱动程序的 InterruptSpinLock 之前,KeSynchronizeExecution 会在第二个处理器上旋转,从而阻止 AccessDevice 接触 SynchronizeContext。 但是, KeSynchronizeExecution 还会将第二个处理器上的 IRQL 提升到中断对象的 SynchronizeIrql,从而防止在该处理器上发生另一个设备中断,以便在 ISR 返回后立即在 DIRQL 中运行 AccessDevice。 但是,对于其他设备,较高的 DIRQL 中断、时钟中断和电源故障中断仍可能发生在任一处理器上。
当 ISR 将驱动程序的 DpcForIsr 排队并返回时,AccessDevice 将在关联中断对象的 SynchronizeIrql 的第二个处理器上运行,并访问 SynchronizeContext。 同时, DpcForIsr 在另一个处理器上运行,DISPATCH_LEVEL IRQL。 DpcForIsr 也已准备好访问 SynchronizeContext,因此它使用 StartIo 例程在步骤 1 中所做的相同参数调用 KeSynchronizeExecution。
当 KeSynchronizeExecution 获取旋转锁并代表 StartIo 例程运行 AccessDevice 时,驱动程序提供的同步例程 AccessDevice 将被授予对 SynchronizeContext 的独占访问权限。 由于 AccessDevice 在 SynchronizeIrql 上运行,因此驱动程序的 ISR 在释放旋转锁之前无法获取旋转锁并访问同一区域,即使设备在另一个处理器上中断,而 AccessDevice 正在执行。
AccessDevice 返回并释放旋转锁。 StartIo 例程将在第二个处理器DISPATCH_LEVEL继续运行。 KeSynchronizeExecution 现在在第三个处理器上运行 AccessDevice,因此它可以代表 DpcForIsr 访问 SynchronizeContext。 但是,如果在步骤 2 中名为 KeSynchronizeExecution 的 DpcForIsr 之前发生了设备中断,则 ISR 可能会在 KeSynchronizeExecution 获取旋转锁并在第三个处理器上运行 AccessDevice 之前在另一个处理器上运行。
如上图所示,虽然在一个处理器上运行的例程持有一个旋转锁,但尝试获取该旋转锁的所有其他例程都没有完成任何工作。 每个尝试获取已持有的旋转锁的例程只需在其当前处理器上旋转,直到支架释放旋转锁。 释放旋转锁时,只有一个例程可以获取它;当前尝试获取相同旋转锁的每个其他例程将继续旋转。
任何旋转锁的持有者在引发的 IRQL 上运行:在执行旋转锁的 DISPATCH_LEVEL 处运行,或者在 DIRQL 处运行中断旋转锁。 KeAcquireSpinLock 和 KeAcquireInStackQueuedSpinLock 的调用方在 DISPATCH_LEVEL 运行,直到调用 KeReleaseSpinLock 或 KeReleaseInStackQueuedSpinLock 以释放锁。 KeSynchronizeExecution 的调用方自动将当前处理器上的 IRQL 提升到中断对象的 SyncIrql,直到调用方提供的 SynchCritSection 例程退出,KeSynchronizeExecution 返回控件。 有关详细信息,请参阅 调用使用旋转锁的支持例程。
请记住以下有关使用旋转锁的事实:
在较低 IRQL 下运行的所有代码都无法在由旋转锁支架和其他尝试获取同一个旋转锁的例程占用的处理器集上完成任何工作。
因此,尽量减少驱动程序持有旋转锁的时间可显著提高驱动程序性能,并显著改善整体系统性能。
如上图所示,内核中断处理程序以先到先得的原则执行多处理器计算机中在同一 IRQL 上运行的例程。 内核还执行以下操作:
当驱动程序例程调用 KeSynchronizeExecution 时,内核会导致驱动程序的 SynchCritSection 例程在调用 KeSynchronizeExecution 的同一处理器上运行, (请参阅步骤 1 和 3) 。
当驱动程序的 ISR 将其 DpcForIsr 排队时,内核会导致 DPC 在 IRQL 低于DISPATCH_LEVEL的第一个可用处理器上运行。 这不一定与 IoRequestDpc 调用的处理器相同, (请参阅步骤 2) 。
驱动程序的中断驱动的 I/O 操作可能倾向于在单处理器计算机中进行序列化,但相同的操作在 SMP 计算机中可能真正异步。 如上图所示,在 DpcForIsr 开始处理 ISR 已处理 CPU1 上的设备中断的 IRP 之前,驱动程序的 ISR 可以在 SMP 计算机的 CPU4 上运行。
换句话说,不应假定中断旋转锁可以保护 ISR 在一个处理器上运行时保存的操作特定数据,以免在 DpcForIsr 或 CustomDpc 例程运行之前在另一个处理器上发生设备中断时被 ISR 覆盖。
尽管驱动程序可以尝试序列化所有中断驱动的 I/O 操作以保留 ISR 收集的数据,但该驱动程序在 SMP 计算机中的运行速度不会比单处理器计算机快得多。 若要在跨单处理器和多处理器平台保持可移植性的同时获得最佳驱动程序性能,驱动程序应使用其他一些技术来保存 ISR 获取的特定于操作的数据,以供 DpcForIsr 进行后续处理。
例如,ISR 可以将特定于操作的数据保存在它传递给 DpcForIsr 的 IRP 中。 此方法的改进是实现 DpcForIsr,该 DpcForIsr 可查询 ISR 增强的计数,使用 ISR 提供的数据处理计数的 IRP 数,并在返回之前将计数重置为零。 当然,该计数必须由驱动程序的中断旋转锁保护,因为其 ISR 和 SynchCritSection 将动态维护其值。