3.9 Kernel Dispatcher Objects

As mentioned in Section 3.7, the NT Kernel defines the object type for timer objects, which can be associated with driver-supplied CustomTimerDpc routines. A timer object is one of a set of Kernel-defined object types called dispatcher objects.

The NT Kernel also defines the types for other dispatcher objects, such as events, semaphores, and mutexes. The Kernel-defined thread object, representing a thread of execution within the system, is also a dispatcher object.

Dispatcher objects can be used as synchronization mechanisms within a nonarbitrary thread context while running at IRQL PASSIVE_LEVEL.

Dispatcher Objects State and Thread Priorities

Every Kernel-defined dispatcher object type has a state that is either set to Signaled or set to Not-Signaled.

A group of threads can synchronize their operations by having one or more threads call KeWaitForSingleObject, KeWaitForMutexObject, or KeWaitForMultipleObjects with dispatcher object(s) and waiting until another routine or thread sets the dispatcher object(s) to the Signaled state.

When a thread calls the KeWaitForSingleObject with a dispatcher object or KeWaitForMutexObject with a mutex, the thread is put into a wait state until the dispatcher object is set to the Signaled state. When a thread calls KeWaitForMultipleObjects with one or more dispatcher objects, the thread is put into a wait state until one or all of the dispatcher objects are set to the Signaled state.

Whenever a dispatcher object is set to the Signaled state, the Kernel changes the state of any thread waiting on that object to ready: the thread will be scheduled to run according to its current runtime priority and the current availability of processor(s) for any thread with that priority. Synchronization events and synchronization timers are exceptions to this rule; when a synchronization event or timer is signaled, only one waiting thread is set to the ready state.

Some NT drivers create their own driver- or device-dedicated system threads and set their thread’s base priority to the lowest real-time priority value. Other highest-level drivers, particularly NT file system drivers, use system worker threads with a base priority that is usually set to the highest variable priority value. The Kernel schedules a thread with the lowest real-time priority to run ahead of every thread with a variable priority, which includes almost every user-mode thread in the system.

Most standard NT driver routines are run in an arbitrary thread context, ahead of all threads that are currently in the ready state.

Threads, whatever their respective runtime priorities, are run at IRQL PASSIVE_LEVEL. Many standard NT driver routines are run at raised IRQL: that is, at a hardware priority greater than PASSIVE_LEVEL, such as IRQL DISPATCH_LEVEL or DIRQL.

Waiting on Dispatcher Objects in NT Drivers

For most NT device and intermediate drivers, the Kernel’s dispatcher objects can be used for synchronization only in the following circumstances:

However, lower-level drivers’ Dispatch routines cannot wait on a Kernel-defined dispatcher object for the completion of an inherently asynchronous I/O operation.

    NT miniport driver writers should consider the following a design guideline:

For portability, miniport drivers should not use kernel dispatcher objects. Miniport drivers should call only ScsiPortXxx routines.

    NT driver writers should consider the following a design guideline:

An NT driver cannot and must not attempt to wait on a dispatcher object for the completion of a transfer operation to or from a paging device.

In other words, NT driver Dispatch routines for read/write requests generally cannot wait on a dispatcher object. A Dispatch routine for any device I/O control request with an I/O control code whose transfer-type value is other than METHOD_BUFFERED also cannot wait on a dispatcher object. For more information about device-type-specific requests that NT drivers must support and I/O control codes, see the Kernel-Mode Driver Reference.

Every other standard NT driver routine is run in an arbitrary thread context: that of whatever thread happens to be current when the driver routine is called to process an IRP or to handle a device interrupt. Moreover, most standard driver routines are run at raised IRQL, either at DISPATCH_LEVEL, or for device drivers, at DIRQL.

    NT driver designers should always keep in mind the following fact:

It is a fatal error to wait on a Kernel dispatcher object for a nonzero interval at raised IRQL.

Driver Dispatch routines that initiate asynchronous I/O operations to a device and all driver routines that run at raised IRQL cannot use dispatcher objects to synchronize their processing of IRPs.

For a summary of the IRQLs at which standard NT driver routines run, see Chapter 16.

Few NT device and intermediate drivers set up driver-created system threads. Few highest-level NT device drivers use system worker threads, as NT FSDs usually do. Consequently, most NT device and intermediate drivers can use a dispatcher object only while the driver is initializing, unloading, or in a Dispatch routine for a synchronous I/O request.

For example, the DriverEntry routines of the system’s NT SCSI class drivers set up an event object so they can do the following:

  1. Call IoBuildSynchronousFsdRequest with the pointer to the driver-supplied storage for an initialized event object.

  2. Set up the IRP that this routine creates and send it with IoCallDriver down to the NT SCSI port driver to request configuration information.

  3. Call KeWaitForSingleObject to wait on the event until the port driver returns the IRP with IoCompleteRequest, which sets the associated event to the Signaled state indicating that the requested configuration information or an error status is available for the class driver.

The following subsections describe the timer, event, semaphore, and mutex dispatcher objects for driver writers who either design drivers with device-dedicated threads and/or worker-thread callback routines, or who design drivers that must wait on one or more dispatcher objects in their DriverEntry, Reinitialize, Unload, or Dispatch routines for synchronous I/O operations.

Any driver that waits on a dispatcher object in its DriverEntry, Reinitialize, or Unload routine runs in a system thread context. In effect, any driver that waits on a dispatcher object in its Dispatch routine for a synchronous I/O operation steals cycles from an arbitrary thread’s quantum.

For simplicity, the following subsections illustrate calls to KeWaitForSingleObject. For more information about KeWaitForMutexObject and KeWaitForMultipleObjects, see the Kernel-Mode Driver Reference.