Any NT driver can set up a timer object with KeInitializeTimer or KeInitializeTimerEx that it can use within a nonarbitrary thread context to time out operations within the driver’s other routines or perform a periodic operation.
A timer can be a notification timer or a synchronization timer. When a notification timer is signaled, all waiting threads have their wait satisfied and the state of the timer remains signaled until it is explicitly reset. When a synchronization timer expires, its state is set to Signaled until a single waiting thread is released and then the timer is reset to the Not-Signaled state. KeInitializeTimer always creates notification timers. KeInitializeTimerEx accepts a Type parameter which can be NotificationTimer or SynchronizationTimer. Both notification and synchronization timers can optionally have an associated CustomTimerDpc routine.
A timer can expire just once or it can be set to expire repeatedly at a given interval. KeSetTimer always sets a timer that will expire just once. KeSetTimerEx accepts an optional Period parameter for specifying a recurring interval for the timer.
Figure 3.23 illustrates using a notification timer to set up a time-out interval for an operation and then wait while other driver routines process an I/O request.
Figure 3.23 Waiting on a Timer Object
As Figure 3.23 shows, a driver must provide storage for the timer object, which must be initialized in the DriverEntry or Reinitialize routine by calling KeInitializeTimer with a pointer to this storage.
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 on its timer object as shown in Figure 3.23:
Note that the thread (or driver routine running in a system thread) passes a NULL pointer for the DPC object previously shown in Figure 3.20 when it calls KeSetTimer if it waits on the timer object instead of queueing a CustomTimerDpc routine.
NT driver routines that run at raised IRQL can time out requests by using a timer object with an associated DPC object, as already described in Section 3.6, 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 Figure 3.23.
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 the section on polling a device in Chapter 16. See also the Kernel-Mode Driver Reference for the specifics of calling KeDelayExecutionThread.
NT drivers’ DriverEntry, Reinitialize, and Unload routines also run in a system thread context, so NT 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 NT drivers generally use another synchronization mechanism in their DriverEntry and/or Reinitialize routines instead of using a timer object. Higher-level NT 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.
For more information about the FILE_DEVICE_XXX that NT drivers set in their device objects, see Section 3.2, and see also the Kernel-Mode Driver Reference.
If the system time changes before a timer expires, relative timers are not affected but the system adjusts absolute timers. A relative timer always expires after the specified number of time units elapse, regardless of the absolute system time. An absolute timer expires at a specific system time, so a change in the system time changes the wait duration of an absolute timer.