An NTDEV poster asked about interlocked list manipulation functions, and about the DDK’s insistence that calls to those functions not be mixed with non-interlocked functions. I answered that there was no difference between calling an ExInterlocked* function and grabbing that lock in the normal way followed by using non-interlocked ops.
Doron Holan from Microsoft clarified my response by saying that the lock is acquired at the highest IRQL in the machine, which prevents priority inversion deadlocks with things like ISRs. I felt temporarily dumb for forgetting about that case, but I thought the way Doron phrased it was a little odd.
So, I decided to take my own advice to the OP and see what these functions really do under the hood. Picking on ExfInterlockedPopEntryList:
lkd> u ExfInterlockedPopEntryList nt!ExfInterlockedPopEntryList: 804e2e68 9c pushfd 804e2e69 fa cli 804e2e6a 8b01 mov eax,[ecx] 804e2e6c 0bc0 or eax,eax 804e2e6e 7406 jz EmptyList 804e2e70 8b10 mov edx,[eax] 804e2e72 8911 mov [ecx],edx 804e2e74 9d popfd 804e2e75 c3 ret EmptyList: 804e2e76 9d popfd 804e2e77 33c0 xor eax,eax 804e2e79 c3 ret
This is a UP kernel, but you should still need to grab the lock at high IRQL to mask off any higher-IRQL threads that might want it, to prevent exactly the priority inversion Doron discussed.
The answer is the little cli
instruction at the top of the routine. Totally disabling interrupts on the chip is equivalent to the highest possible IRQL
, and does exactly the job we needed. The popfd
restores the interrupt enable flag (among others) to its previous condition.
The MP kernel looks about the same, with the addition that the routine actually spins on the supplied lock – unnecessary for this UP kernel.