天天看點

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