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