As its name suggests, a mutex object is a synchronization mechanism designed to ensure mutually exclusive access to a single resource shared among a set of kernel-mode threads. Only highest-level drivers, such as FSDs that use executive worker threads, are likely to use a mutex object.
Possibly, a highest-level driver with driver-created threads and/or worker-thread callback routines might use a mutex object. However, any driver with pageable thread(s) or worker-thread callbacks must manage the acquisitions of, waits on, and releases of its mutex object(s) very carefully.
As already mentioned in the preceding section, NT mutex objects have built-in features that provide system (kernel-mode only) threads mutually exclusive, deadlock-free access to shared resources in SMP machines. Every NT mutex object has two basic built-in features that provide this protection for resources shared between or among system threads:
The NT Kernel assigns ownership of a given mutex to a single thread at a time, which has the following effects:
A thread can acquire ownership of a mutex object that it already owns (recursive ownership), but a recursively acquired mutex object is not set to the Signaled state until the thread releases its ownership completely. Such a thread must explicitly releases the mutex as many times as it acquired ownership before another thread can acquire the mutex.
The Kernel never permits a thread that owns a mutex to cause a transition to user mode without first releasing the mutex and setting it to the Signaled state. If any FSD- or driver-created thread that owns a mutex attempts to return control to the I/O Manager before releasing ownership of the mutex, the Kernel brings down the system.
If a driver thread acquires more than one mutex, the driver should be designed such that the Level of a given mutex object determines whether such a thread can acquire ownership of the mutex at any particular time. In such a case, the driver should enforce the following policy:
Thus, the owner of a mutex must not attempt to acquire another mutex with a lower Level since this other mutex might already be owned by another thread that is trying to acquire the first thread’s higher Level mutex. If a driver does not enforce this policy, the scenario that results would cause deadlocks since neither thread could acquire ownership of the other thread’s currently held mutex.
Any NT driver that uses a mutex object must call KeInitializeMutex once before it waits on or releases its mutex object. Figure 3.26 illustrates how two system threads might use a mutex object.
Figure 3.26 Waiting on a Mutex Object
As Figure 3.26 shows, such a driver must provide the storage for the mutex object. Because drivers can wait on and release mutexes only within a thread context, a driver that creates its own system threads can provide storage for its mutex objects in paged pool, allocated by the driver.
If such a highest-level driver uses system worker threads or waits on the mutex in its Dispatch routines, it must provide resident storage in the device extension of a driver-created device object (see Section 3.2), in the controller extension if it uses a controller object (see Section 3.4), or in nonpaged pool allocated by the driver.
When the DriverEntry or Reinitialize routine calls KeInitializeMutex it must pass a pointer to the driver’s storage for the mutex object, which the Kernel initializes to the Signaled state. In addition, the caller can specify a nonzero Level for the mutex object, as shown in Figure 3.26 to control the order of acquisition if the caller uses more than one mutex.
After such a highest-level driver has initialized, it can manage mutually exclusive access to a shared resource as shown in Figure 3.26. For example, a driver’s Dispatch routines for inherently synchronous operations and thread(s) might use a mutex to protect a driver-created queue for IRPs.
Because KeInitializeMutex always sets the initial state of a mutex object to Signaled, as Figure 3.26 shows:
If a thread acquires ownership of a mutex object recursively, that thread must explicitly call KeReleaseMutex as many times as it waited on the mutex in order to set the mutex object to the Signaled state. For example, if a thread calls KeWaitForSingleObject and then KeWaitForMutexObject with the same Mutex pointer, it must call KeReleaseMutex twice when it acquires the mutex in order to set that mutex object to the Signaled state.
Calling KeReleaseMutex with the Wait parameter set to TRUE indicates the caller’s intention to immediately call a KeWait..Object(s) support routine on return from KeReleaseMutex.
A pageable thread or pageable driver routine that runs at IRQL PASSIVE_LEVEL should never call KeReleaseMutex with the Wait parameter set to TRUE. Such a call causes a fatal page fault if the caller happens to be paged out between the calls to KeReleaseMutex and KeWait..Object(s).
Any standard driver routine that runs at an IRQL greater than PASSIVE_LEVEL cannot wait for a nonzero interval on any dispatcher object(s) without bringing down the system (see Section 3.9,). However, such a routine can call KeReleaseMutex if it owns the mutex while running at an IRQL less than or equal to DISPATCH_LEVEL.
For a summary of the IRQLs at which standard NT driver routines run, see Chapter 16. For support-routine-specific IRQL requirements, see the Kernel-Mode Driver Reference.