3.9.4 Mutex Objects

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:

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:

  1. A Dispatch routine’s initial call to KeWaitForSingleObject with the Mutex pointer puts the current thread immediately into the ready state, gives the thread ownership of the mutex, and resets the mutex state to Not-Signaled. As soon as the Dispatch routine resumes running, it can safely insert an IRP into the mutex-protected queue.

  2. When a second thread (another Dispatch routine, driver-supplied worker-thread callback routine, or driver-created thread) calls KeWaitForSingleObject with the Mutex pointer, the second thread is put into the wait state.

  3. When the Dispatch routine finishes queueing the IRP as described in Step 1, it calls KeReleaseMutex with the Mutex pointer and a Boolean Wait value, which indicates whether it intends to call KeWaitForSingleObject (or KeWaitForMutexObject) with the Mutex as soon as KeReleaseMutex returns control.

  4. Assuming the Dispatch routine released its ownership of the mutex in Step 3 (Wait set to FALSE), the mutex is set to the Signaled state by KeReleaseMutex. The mutex currently has no owner, so the Kernel determines whether another thread is waiting on that mutex. If so, the Kernel makes the second thread (see Step 2) the mutex owner, possibly boosts the thread’s priority to the lowest real-time priority value, and changes its state to ready.

  5. The Kernel dispatches the second thread for execution as soon as a processor is available: that is, no other thread with a higher priority is currently in the ready state and there are no kernel-mode routines to be run at raised IRQL (greater than PASSIVE_LEVEL). The second thread (a Dispatch routine queueing an IRP, or the driver’s worker-thread callback routine or driver-created thread dequeueing an IRP) can now safely access the mutex-protected queue of IRPs until it calls KeReleaseMutex.

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.

    NT driver writers should consider the following guidelines for setting the Wait parameter to 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.