Numbered Queued Spinlocks in the WRK

Windows Research Kernel @ HPI

In a recent post, we covered the implementation of in-stack queued spinlocks, the recommended method to use queued spinlocks in drivers. In this article, we would like to extend the discussion to a second class of queued spinlocks: numbered queued spinlocks. These well-known locks use per processor, pre-allocated memory to store the processor's queue item. In this article, we will give a short overview over the existing queued spinlocks in the WRK, how they are initialized and how they are used.

The following queued spinlocks are defined in the WRK (public\sdk\inc\ntkeapi.h:128). Please note that this definition is contained in every Windows Driver Development Kit (WDK) as well.

typedef enum _KSPIN_LOCK_QUEUE_NUMBER {
    LockQueueDispatcherLock,
    LockQueueUnusedSpare1,
    LockQueuePfnLock,
    LockQueueSystemSpaceLock,
    LockQueueVacbLock,
    LockQueueMasterLock,
    LockQueueNonPagedPoolLock,
    LockQueueIoCancelLock,
    LockQueueWorkQueueLock,
    LockQueueIoVpbLock,
    LockQueueIoDatabaseLock,
    LockQueueIoCompletionLock,
    LockQueueNtfsStructLock,
    LockQueueAfdWorkQueueLock,
    LockQueueBcbLock,
    LockQueueMmNonPagedPoolLock,
    LockQueueUnusedSpare16,
    LockQueueTimerTableLock,
    LockQueueMaximumLock = LockQueueTimerTableLock + LOCK_QUEUE_TIMER_TABLE_LOCKS
} KSPIN_LOCK_QUEUE_NUMBER, *PKSPIN_LOCK_QUEUE_NUMBER;

The processor control block (KPRCB) contains a field LockQueue (base\ntos\inc\i386.h:1109, for x86 platforms), which is an array of LockQueueMaximumLock KSPIN_LOCK_QUEUE elements. The KSPIN_LOCK_QUEUE structure has the following layout:

typedef struct _KSPIN_LOCK_QUEUE
{
    struct _KSPIN_LOCK_QUEUE * volatile Next;
    PKSPIN_LOCK volatile Lock;
} KSPIN_LOCK_QUEUE, *PKSPIN_LOCK_QUEUE;

The first field (Next) is used for queueing up multiple queue items when multiple processors compete for a spinlock. The Lock field is pointer to the spinlock that should be acquired. When using numbered spinlocks, this field must be initialized prior to invoking any acquire/release function, as otherwise the mapping from the queue number to the actual lock wouldn't work. The WRK therefore initializes the set of numbered queued spinlocks upon system initalization in the KiInitSpinLocks (base\ntos\ke\kiinit.c:131), for which we show an excerpt here:

    // further function code omitted here ...  

    Prcb->LockQueue[LockQueueDispatcherLock].Next = NULL;
    Prcb->LockQueue[LockQueueDispatcherLock].Lock = &KiDispatcherLock;

    Prcb->LockQueue[LockQueueUnusedSpare1].Next = NULL;
    Prcb->LockQueue[LockQueueUnusedSpare1].Lock = NULL;

    Prcb->LockQueue[LockQueuePfnLock].Next = NULL;
    Prcb->LockQueue[LockQueuePfnLock].Lock = &MmPfnLock;

    // further function code omitted here ...

To acquire a queued spinlock the following API can be used:

KIRQL
FASTCALL
KeAcquireQueuedSpinLock (
    __in KSPIN_LOCK_QUEUE_NUMBER Number
    );

That is, knowing the queue number is enough information to acquire the queued spinlock on the current processor. The acquire and release process is the same for in-stack and numbered queued spinlocks, which we covered here. For the sake of completeness, we present the mapping betwen spinlock queue number and the actual lock here:

  • LockQueueDispatcherLock - KiDispatcherLock
  • LockQueuePfnLock - MmPfnLock
  • LockQueueSystemSpaceLock - MmSystemSpaceLock
  • LockQueueBcbLock - CcBcbSpinLock
  • LockQueueMasterLock - CcMasterSpinLock
  • LockQueueNonPagedPoolLock - NonPagedPoolLock
  • LockQueueWorkQueueLock - CcWorkQueueSpinLock
  • LockQueueVacbLock - CcVacbSpinLock
  • LockQueueIoCancelLock - IopCancelSpinLock
  • LockQueueIoVpbLock - IopVpbSpinLock
  • LockQueueIoDatabaseLock - IopDatabaseLock
  • LockQueueIoCompletionLock - IopCompletionLock
  • LockQueueNtfsStructLock - NtfsStructLock
  • LockQueueAfdWorkQueueLock - AfdWorkQueueSpinLock
  • LockQueueMmNonPagedPoolLock - MmNonPagedPoolLock

Please note the two locks MmNonPagedPoolLock and NonPagedPoolLock. It's interesting that they share the same name except for the prefix. But we will cover them in a later post.

The question about numbered queued spinlock that remains is that if they are basically the same as in-stack queued spinlocks, why are they used anyway? Well, the ultimate answer can only be given by a responsible kernel developer. However, in our opinion, the reason for the "invention" of numbered queued spinlocks has its cause probably in software evolution. When queued spinlocks were introduced to the kernel, most of the important, high-contention locks were transformed from ordinary spinlocks to queued spinlocks. Unfortunately the API for queued spinlocks is different than the spinlock version, as the queued spinlocks require a lock handle for the release operation. With providing the numbered queue indirection, storing the handle and refactoring the code is reduced to a minimum (as for changing the signature of the acquire/release functions) and more or less transparent for the rest of the kernel. But that's just my opinion. If you do not agree on that, please let us know 🙂

Comments

One Response to "Numbered Queued Spinlocks in the WRK"

  1. fastcall on March 28th, 2010 19:19

    [...] in registers, when possible. The following list shows the implementation of this calling convention.Windows Research Kernel @ HPI - news, howtos and …Name (required) Mail (will not be published) (required) Website. You can use these tags: Windows [...]