You will notice very often, there is always some form of synchronization mechanism within each call stack you examine from a dump file. The next few blog posts, will attempt to explain these synchronization mechanisms, and potentially how they can cause bugchecks if not used correctly. In this blog post I’m going to explain spinlocks and queued spinlocks.
Spinlocks are a form of locking mechanism which is used by the kernel to enforce the concept of mutual exclusion. Typically, spinlocks are used in conjunction with critical regions, and are used to allow only one thread to execute code within that critical region. Spinlocks are used at IRQL Level 2 or Dispatch Level, and therefore are allocated with non-paged pool.
Spinlocks are given the associated level of IRQL Level 2, in order to mask or preempt any dispatching mechanisms and interrupts at that level or below, to allow the thread within the critical region to execute the code much more quickly, this is because when another thread is attempting to acquire the spinlock, the thread and the processor (where the thread is running) is kept waiting (or spinning) until the lock is obtained. This is a expensive operation, and is usually shown as 100% CPU usage.
Spinlocks are generally acquired by using a test and set operation (written in Assembly) which is completed within one atomic instruction, to prevent other threads from interrupting and then acquiring the spinlock. The lock bts Assembly instruction is typically used, to lock the processor bus and stop other processors from interrupting. The test and set operation is used to test the lock variable, here is a example of a spinlock acquisition in Assembly code:
|Source: Spinlock Wiki Article|
Since, using Spinlocks can be expensive, a Intel based Assembly instruction can be introduced, called pause. This is designed to reduce power consumption and prevent the CPU from having to re-order many read requests from waiting threads when the Spinlock is released.
It’s important to remember, that any thread holding a Spinlock, isn’t able to cause any page faults or call any dispatch routines, since this will result in a system crash and most likely bugcheck.
As you may have established, Spinlocks are not the most efficient locking mechanisms in terms of performance, and as a result queued spinlocks were created exclusively for the Kernel. They are not supported for the use by third-party driver developers.
A queued spinlock works similarly to a conventional spinlock, however, when a processor and it’s thread wish to acquire a spinlock being held, they are placed in a queue for that spinlock. The queue uses a FIFO (First In – First Out) ordering mechanism, the processor with the spinlock will give the spinlock to the next processor identifier in the queue. The processor technically adds it’s processor identifier to the queue.
We can view the number of global queued spinlocks with the !qlocks extension:
There are currently no acquired queued spinlocks in this example, however, the owner will be specified if there is such a spinlock.
Instack Queued Spinlocks:
These are the queued spinlocks which have been made to third-party driver developers, they still use the same _KSPIN_LOCK data structure, but these spinlocks use a handle to a data structure called KLOCK_QUEUE_HANDLE, the handle is local to the stack of the calling thread.
Spinlock Common Problems:
A list of problems and their preventions with use of Spinlocks can be found here on the MSDN page – Preventing Errors and Deadlocks While Using Spinlocks