1.尽早释放自旋锁,因为拥有它,其他cpu的活动就要被阻止。
这个没什么好说的
2.拥有自旋锁时不要引起硬件或软件异常,否则系统会崩溃
3.拥有自旋锁是不要访问任何分页代码或数据。
2和3得放一起讨论。这里引用MSDN上的一篇文章:Preventing Errors and Deadlocks While Using Spin Locks 这篇文章中罗列了拥有SpinLock时不应该触发的异常。粗看很疑惑,为什么不能在此期间触发异常?那换个说法,在IRQL>=DISPATCH_LEVEL时,不能使用分页内存,这个驱动开发规则大家都能理解和接受吧?其实2和3是这个规则的演化版,看下KeAcquireSpinLock的实现:
VOID
NTAPI
KeAcquireSpinLock(PKSPIN_LOCK SpinLock,
PKIRQL OldIrql)
{
/* Call the fastcall function */
*OldIrql = KfAcquireSpinLock(SpinLock);
}
KIRQL FASTCALL
KfAcquireSpinLock (
PKSPIN_LOCK SpinLock
)
{
KIRQL OldIrql;
ASSERT(KeGetCurrentIrql() <= DISPATCH_LEVEL);
OldIrql = KfRaiseIrql(DISPATCH_LEVEL);
KiAcquireSpinLock(SpinLock);
return OldIrql;
}
KeAcquireSpinLock调用KfAcquireSpinLock.KfAcquireSpinLock干的第一件事就是提升IRQL到DISPATCH_LEVEL。因此直到释放自旋锁,使得IRQL降低到DISPATCH_LEVEL一下,整个代码段都在DISPATCH_LEVEL级别上运行,因此,这个IRQL级别上需要遵守的规则在KeAcquireSpinLock函数中也得遵守
4.拥有自旋锁时,不能调用IoStartNextPacket和IoCompleteRequest例程,一定不要在取消例程中调用 IoAcquireCancelSpinLock,这会引起系统死锁
拥有自旋锁时不能调用IoStartNextPacket我觉得有点牵强。IoStartNextPacket和IoStartPacket是一对好朋友,既然IoStartNextPacket不能调用了IoStartPacket也应该被同等对待。目前猜测可能是因为执行IoStartNextPacket时会依次执行设备队列中挂起的Irp请求,这违反了要尽早释放自旋锁的初衷。(这个解释我自己都不怎么信服);
对于 IoCompleteRequest的禁忌原因,我觉得可能是因为下列情形: 如果某层设备栈设置了完成例程,那么当调用IoCompleteRequest后执行设备栈回滚时,回滚到完成例程中。再进一步假设这个完成例程把下层返回上来的Irp拆成多个子Irp,以同步方式调用IoCallDriver下发这些IRP,这势必会长时间造成等待。这也与尽早释放自旋锁的初衷相违背;
至于不要在取消例程中调用 IoAcquireCancelSpinLock,这个可以看下IoCancelIrp的实现:
BOOLEAN
NTAPI
IoCancelIrp(IN PIRP Irp)
{
KIRQL OldIrql;
PDRIVER_CANCEL CancelRoutine;
IOTRACE(IO_IRP_DEBUG,
"%s - Canceling IRP %p\n",
__FUNCTION__,
Irp);
ASSERT(Irp->Type == IO_TYPE_IRP);
/* Acquire the cancel lock and cancel the IRP */
IoAcquireCancelSpinLock(&OldIrql);
Irp->Cancel = TRUE;
/* Clear the cancel routine and get the old one */
CancelRoutine = IoSetCancelRoutine(Irp, NULL);
if (CancelRoutine)
{
...
CancelRoutine(IoGetCurrentIrpStackLocation(Irp)->DeviceObject, Irp);
return TRUE;
}
IoReleaseCancelSpinLock(OldIrql);
return FALSE;
}
在调用取消函数之前,IoCancelIrp已经调用过
IoAcquireCancelSpinLock,如果在取消函数中再次调用这个就会因递归调用自旋锁而造成死锁