Interlocked list manipulation functions

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.

Leave a Reply