天天看点

wdk中使用自旋锁SpinLock的规则及原因分析

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;
}