Using KeAcquireSpinLockAtDpcLevel is only a perf gain if you know you are DISPATCH_LEVEL
Well, that is certainly a long title ;).
First, let us look at an approximate implementation of KeAcquireSpinLock and KeRaiseIrql (and yes I know that KeRaiseIrql is really a #define to KfRaiseIrql, but it is the same thing that happens in the end…)
KIRQL KeAcquireSpinLock(PKSPIN_LOCK SpinLock, PKIRQL PreviousIrql)
{
KeRaiseIrql(DISPATCH_LEVEL, PreviousIrql);
[spin on the lock until it has been acquired]
}
VOID KeRaiseIrql(KIRQL NewIrql, PKIRQL, PKIRQL OldIrql)
{
OldIrql = KeGetCurrentIrql();
[raise IRQL to NewIrql]
}
What I want to emphasize is that KeAcquireSpinLock will retrieve the current IRQL (to know what to restore the IRQL to when the lock is released) as a part of acquiring the spin lock. Retrieving the current irql is a relatively expensive operation. Enter KeAcquireSpinLockAtDpcLevel. KeAcquireSpinLockAtDpcLevel does away with the IRQL change and just implements [spin on the lock until it has been acquired] , but it does this with a large caveat…you must be running at DISPATCH_LEVEL (in reality it requires IRQL >= DISPATCH_LEVEL, but that is another discussion for another day) . It requires DISPATCH_LEVEL or higher so that you do not deadlock. Another caveat to effectively use KeAcquireSpinLockAtLevel you must know 100% that you are at DISPATCH_LEVEL. Naively, one could think that the following code optimizes for both cases
if (KeGetCurrentIrql() < DISPATCH_LEVEL) {
KeAcquireSpinLock(&lock, &oldIrql);
}
else {
KeAcquireSpinLockAtDpcLevel(&lock);
}
But the problem here is that in the case of the current IRQL < DISPATCH_LEVEL, the current IRQL is being retrieved twice (once in your code, once in KeAcquireSpinLock), so this relatively expensive operation is being performed twice when it should be performed only once. What all of this boils down to is that you must know with 100% certainty that the current IRQL is DISPATCH_LEVEL before you can use KeAcquireSpinLockAtDpcLevel effectively. Here are a couple of contexts here you can know for certain that the IRQL is DISPATCH_LEVEL.
- In a DPC (for ISR, for a timer, your own)
- After you have acquired a spin lock. In the case where you have nested locked being acquired (first A, then B), after you have called KeAcquireSpinLock(&A) you can then call KeAcquireSpinLockAtDpcLevel(&B)
Notice that a completion routine is not guaranteed to be called at IRQL == DISPATCH_LEVEL! While you may see that it is called at dispatch, it is not something that you can rely on 100% of the time. For instance, the lower driver could complete the IRP at passive level in an error condition.
Comments
- Anonymous
December 10, 2008
D'oh! Time to rewrite those locks... q->OldIrql = KeGetCurrentIrql(); if (DISPATCH_LEVEL <= q->OldIrql) { KeAcquireInStackQueuedSpinLockAtDpcLevel(lock->sl, q); } else { KeAcquireInStackQueuedSpinLock(lock->sl, q); } Thanks for the great blog. Please keep 'em coming.