The thread is the basic unit of execution in Windows. However, Windows does not create a thread in which to run a KMDF driver, and most KMDF drivers do not create threads. Instead, a driver is a group of functions that are called in an existing thread that is "borrowed" from an application or system component. The existing thread has a context that limits the data that the driver can access. It is important to understand the thread context in which certain driver callbacks run if you are creating drivers that perform some types of I/O operations.
Windows schedules individual threads, not entire processes, for execution. Every thread has a scheduling priority-its thread priority-which is a value from 0 to 31, inclusive. Higher numbers indicate higher priority threads.
Each thread is scheduled for a quantum, which defines the maximum amount of CPU time that the thread can run before the kernel looks for other threads at the same priority to run. The exact duration of a quantum varies depending on what version of Windows is installed, the type of processor on which Windows is running, and the performance settings that the system administrator establishes.
After a thread is scheduled, it runs until one of the following occurs:
The thread's quantum expires.
The thread enters a wait state.
A higher-priority thread becomes ready to run.
Kernel-mode threads do not have priority over user-mode threads. A kernel-mode thread can be preempted by a user-mode thread that has a higher scheduling priority.
Thread priorities in the range 1–15 are called dynamic priorities. Thread priorities in the range 16–31 are called real-time priorities. Thread priority zero is reserved for the zero-page thread, which zeroes free pages for use by the memory manager.
Every thread has a base priority and a current priority:
The base priority is usually inherited from the base priority for the thread's process.
The current priority is the thread's priority at any given time.
For kernel-mode driver code that runs in the context of a user thread, the base priority is the priority of the user process that originally requested the I/O operation. For kernel-mode driver code that runs in the context of a system worker thread, such as a work item, the base priority is the priority of the system worker threads that service its queue.
To improve system throughput, Windows sometimes adjusts thread priorities. If a thread's base priority is in the dynamic range, Windows can temporarily increase (that is, "boost") or decrease the priority, thus making its current priority different from its base priority. If a thread's base priority is in the real-time range, its current priority and base priority are always the same; threads running at real-time priorities never receive a priority boost. Furthermore, a thread that is running at a dynamic priority can never be boosted to a real-time priority. Therefore, threads with base priorities in the real-time range always have a higher priority than those in the dynamic range.
Windows can boost a thread's priority when the thread completes an I/O request, when it stops waiting for an event or semaphore, or when it has not been run for some time despite being ready to run (called "CPU starvation"). Threads involved in the graphical user interface (GUI) and the user's foreground process also receive a priority boost in some situations. The amount of the increase depends on the reason for the boost and, for I/O operations, on the type of device involved.
Drivers can affect the boost their code receives by specifying the following:
A priority boost in the call to WdfRequestCompleteWithPriorityBoost.
A priority increment in the call to KeSetEvent, KePulseEvent, or KeReleaseSemaphore.
Constants defined in Wdm.h indicate the appropriate priority boost for each device type, event, and semaphore. By default, the framework applies a default priority boost based on the device type even if the driver completes the request by calling WdfRequestComplete.
Tip See "Specifying Priority Boosts When Completing I/O Requests" in the WDK for more information-online at http://go.microsoft.com/fwlink/?LinkId=81582.
Note A thread's scheduling priority is not the same as the IRQL at which the processor operates while the thread is running.
Kernel-mode software developers use the term "thread context" in two slightly different ways. In its narrowest meaning, thread context is the value of the thread's CONTEXT structure. The CONTEXT structure contains the values of the hardware registers, the stacks, and the thread's private storage areas. The exact contents and layout of this structure vary according to the hardware platform. When Windows schedules a user thread, it loads information from the thread's CONTEXT structure into the user-mode address space.
From a driver development perspective, however, thread context has a broader meaning. For a driver, the thread context includes not only the values stored in the CONTEXT structure, but also the operating environment they define-particularly, the security rights of the calling application. For example, a driver function might be called in the context of a user-mode thread, but it can in turn call a ZwXxx function to perform an operation in the context of the operating system kernel. This chapter uses "thread context" in this broader meaning.
When a driver function performs an I/O operation, the thread context might contain the user-mode address space and security rights of the process that requested the I/O. However, if the calling process was performing an operation on behalf of another user or application, the thread context might contain the user-mode address space and security rights of a different process. In other words, the user-mode address space might contain information that pertains to the process that requested the I/O, or it might instead contain information that pertains to a different process.
The thread context in which KMDF driver functions are called depends on the type of device, on the driver's position in the device stack, and on the other activities currently in progress on the system. KMDF callbacks run in one of three thread contexts:
A few driver functions run in the context of a system thread, which has the address space of the system process and the security rights of the operating system itself. All DriverEntry and EvtDriverDeviceAdd callbacks run in a system thread context, as do work item callbacks queued with the WdfWorkItemEnqueue method. No user-mode requests arrive in a system thread context.
The I/O dispatch functions of the top driver in the device stack-often a WDM file system driver or file system filter driver-receive I/O requests in the context of the thread that initiated the request. These functions can access data in the user-mode address space of the requesting process, but they must validate pointers and protect themselves against user-mode errors.
Most other driver functions are called in an arbitrary thread context. Although the top driver in the stack receives I/O requests in the context of the requesting thread, that driver typically forwards those requests to lower-level drivers that run in different threads.
For example, when a user-mode application requests a synchronous I/O operation, the I/O manager calls the I/O dispatch function of the top driver in the kernel-mode device stack in the context of the thread that requested the operation. The dispatch function queues the I/O request for processing by lower-level drivers. The requesting thread then enters a wait state until the I/O is complete. A different thread dequeues the request, which is handled by lower-level drivers that run in the context of whatever thread is executing at the time they are called.
Consequently, you can make no assumptions about the contents of the user-mode address space or the thread context in which most of a KMDF driver's callback functions run. However, in some cases, you must arrange for a driver function to run in the context of its caller. If your KMDF driver performs neither buffered nor direct I/O-usually called "neither I/O" or METHOD_NEITHER I/O- it receives a user-mode buffer pointer in an IOCTL. The driver can access the pointer only in the context of the thread that issued the request. To access this pointer, therefore, the driver supplies an EvtIoInCallerContext callback, which the framework invokes in the context of the thread that called the driver. If the KMDF driver is the top driver in its device stack, the code in the callback can, with the proper safeguards, access the user-mode buffer pointer to lock the required buffer into memory. However, if one or more filter drivers are layered over the KMDF driver in the device stack, the caller of the KMDF driver might not be the user-mode thread that initiated the request and the pointer might not be valid.
Chapter 8, "I/O Flow and Dispatching," provides more information and a code example of EvtIoInCallerContext.