title | description | keywords | ms.date | ||||||||
---|---|---|---|---|---|---|---|---|---|---|---|
Using Timer Objects |
Describes using timer objects to set up a timeout interval for an operation and then wait while other driver routines process an I/O request. |
|
07/22/2021 |
The following figure illustrates the use of a notification timer to set up a timeout interval for an operation and then wait while other driver routines process an I/O request.
As the previous figure shows, a driver must provide storage for the timer object, which must be initialized by a call to KeInitializeTimer with a pointer to this storage. A driver typically makes this call from its AddDevice routine.
Within the context of a particular thread, such as a driver-created thread or a thread requesting a synchronous I/O operation, the driver can wait for its timer object as shown in the previous figure:
-
The thread calls KeSetTimer with a pointer to the timer object and a given DueTime value, expressed in units of 100 nanoseconds. A positive value for DueTime specifies an absolute time at which the timer object should be removed from the kernel's timer queue and set to the Signaled state. A negative value for DueTime specifies an interval relative to the current system time.
Note that the thread (or driver routine running in a system thread) passes a NULL pointer for the DPC object (shown previously in the figure illustrating using timer and DPC objects for a CustomTimerDpc routine) when it calls KeSetTimer if it waits on the timer object instead of queuing a CustomTimerDpc routine.
-
The thread calls KeWaitForSingleObject with a pointer to the timer object, which puts the thread into a wait state while the timer object is in the kernel's timer queue.
-
The given DueTime expires.
-
The kernel dequeues the timer object, sets it to the Signaled state, and changes the thread's state from waiting to ready.
-
The kernel dispatches the 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 a higher IRQL.
Driver routines that run at IRQL >= DISPATCH_LEVEL can time out requests by using a timer object with an associated DPC object to queue a driver-supplied CustomTimerDpc routine. Only driver routines that run within a nonarbitrary thread context can wait for a nonzero interval on a timer object, as shown in the previous figure.
Like every other thread, a driver-created thread is represented by a kernel thread object, which is also a dispatcher object. Consequently, a driver need not have its driver-created thread use a timer object to voluntarily put itself into a wait state for a given interval. Instead, the thread can call KeDelayExecutionThread with a caller-supplied interval. For more information about this technique, see Polling a Device.
DriverEntry, Reinitialize, and Unload routines also run in a system thread context, so drivers can call KeWaitForSingleObject with a driver-initialized timer object or KeDelayExecutionThread while they are initializing or unloading. A device driver can call KeStallExecutionProcessor for a very short interval (preferably something less than 50 microseconds) if it must wait for the device to update state during its initialization.
However, higher-level drivers generally use another synchronization mechanism in their DriverEntry and Reinitialize routines instead of using a timer object. Higher-level drivers should always be designed to layer themselves over any lower-level driver of a particular type or types of device. Therefore, a higher-level driver tends to become slow to load if it waits on a timer object or calls KeDelayExecutionThread because such a driver must wait for an interval long enough to accommodate the slowest possible device supporting it. Note also that a "safe" but minimum interval for such a wait is very difficult to determine.
Similarly, PnP drivers should not wait for other actions to occur, but instead should use the PnP manager's notification mechanism.