Geoff Chappell - Software Analyst
This function obtains exclusive use of whatever resource is protected by the given lock.
VOID KeAcquireSpinLock (KSPIN_LOCK *SpinLock, KIRQL *OldIrql);
The SpinLock argument is the address of the lock that is to be acquired.
The OldIrql argument is the address of a variable that is to receive the Interrupt Request Level (IRQL) that is to be restored when the lock is released.
The KeAcquireSpinLock function is exported by name from x86 builds of the HAL in all versions and of the kernel in version 6.2 and higher. The HAL’s export in version 6.2 and higher is merely a forward to the kernel.
The closest x64 analogue of this function is KeAcquireSpinLockRaiseToDpc, which is exported by name from the kernel in the version 5.2 from Windows Server 2003 SP1, and higher. Although no function named KeAcquireSpinLock is exported by x64 builds of either the HAL or kernel, programmers have it as a macro defined in WDM.H.
The KeAcquireSpinLock function is documented in all known editions of the Device Driver Kit (DDK) or Windows Driver Kit (WDK) since at least the DDK for Windows NT 3.51.
As early as version 3.50, the KeAcquireSpinLock implementation was shifted to a __fastcall alternative named KfAcquireSpinLock. Starting at least from the Device Driver Kit (DDK) for Windows NT 3.51, a macro definition reinterprets the former in terms of the latter. New code is written as if calling KeAcquireSpinLock but actually calls KfAcquireSpinLock. The old function continues to be exported, presumably to support (old) code that tries to import the function, but its implementation is only a stub that reinterprets in terms of KfAcquireSpinLock just like the macro.
That KfAcquireSpinLock has all the substance is here treated just as a detail of internal organisation. As far as should concern any code outside the kernel or HAL, the behavour is all in KeAcquireSpinLock.
The KeAcquireSpinLock function is in some sense a convenience. Its work is in two parts which can be done independently of KeAcquireSpinLock by calling exported functions, the first from the HAL, the second from the kernel. The KeAcquireSpinLock implementation gains by inlining its two parts (and skipping the second entirely for single-processor builds).
The first part is to raise the IRQL to DISPATCH_LEVEL if it is not already there. This ensures that the current thread has exclusive use of the current processor. Interrupts can occur at higher IRQL, as with hardware interrupts, but they are not permitted to switch the processor to another thread. Equivalent exported functions are KeRaiseIrql, KfRaiseIrql (which each need DISPATCH_LEVEL as an argument) and KeRaiseIrqlToDpcLevel (which does not).
The second part is to obtain the current processor’s exclusive use of the protected resource. All threads that seek to use the resource must acquire the agreed lock. All call this function or a similar one so that all try to mark the lock in the same way but are able to do so only while the lock is not already marked. Typically, they wait in a more or less tight loop until they are able to mark the lock to signify that they now own the resource. Equivalent exported functions for this second part are KeAcquireSpinLockAtDpcLevel and, on x86 builds only, KefAcquireSpinLockAtDpcLevel and KiAcquireSpinLock. This part is trivial in single-processor builds.
The KeAcquireSpinLock function is intended for use at IRQL up to and including DISPATCH_LEVEL. The addresses that are given in the SpinLock and OldIrql arguments must both be of non-paged memory.
The function returns with the IRQL at DISPATCH_LEVEL. No other thread can execute on the same processor. No thread on another processor can acquire the lock. The owning thread must cause no exception, including by touching paged memory. These constraints continue until the owning thread releases the lock, e.g., by calling the KeReleaseSpinLock function.
Spin locks are either owned or not. That is their entire state. If the KeAcquireSpinLock function is called to acquire a spin lock that the calling thread already owns, then the function cannot return (except with contrivance). This is not a deadlock between two threads, just that the one thread is hung in the spin loop, waiting forever for the same thread to release the lock.