Deferred Procedure Calls (DPCs)
Deferred Procedure Calls (DPCs) are a mechanism in the Windows kernel that allows routines to be executed at a later time, typically after an interrupt service routine (ISR) has completed its immediate tasks.
Purpose of DPCs
DPCs are essential for managing hardware interrupts efficiently. When a hardware device generates an interrupt, the system executes an Interrupt Service Routine (ISR). ISRs are designed to be very short and fast, as they run at a high interrupt request level (IRQL) and must quickly acknowledge the interrupt and minimize the time spent disabling other interrupts. However, some tasks triggered by an interrupt might be more complex and time-consuming. These tasks can be deferred and executed by a DPC at a lower IRQL, allowing the system to return to a more responsive state quickly.
How DPCs Work
The process involves the following steps:
- A hardware device generates an interrupt.
- The kernel's ISR runs to acknowledge the interrupt and perform immediate, critical tasks.
- The ISR then queues a DPC routine to be executed later.
- The kernel scheduler, upon returning to a suitable IRQL (typically `DISPATCH_LEVEL`), checks for pending DPCs.
- When the system is ready, it executes the queued DPC routine.
Key Concepts
- IRQL (Interrupt Request Level): DPCs run at
DISPATCH_LEVEL
, which is a high IRQL that allows them to access most kernel data structures but also prevents them from blocking or yielding, as this would make the system unresponsive. - DPC Object: A kernel data structure (
_DPC
) that represents a DPC routine and its associated context. - DPC Queue: The kernel maintains a queue of DPC objects waiting to be executed.
- DPC Routine: The function that contains the code to be executed by the DPC. This routine receives the DPC object and a driver-defined context as parameters.
DPC Routines
A typical DPC routine has the following signature:
VOID
MyDpcRoutine(
PKDPC Dpc,
PVOID DeferredContext,
PVOID SystemArgument1,
PVOID SystemArgument2
);
Dpc
: A pointer to the_DPC
object.DeferredContext
: A pointer to a driver-defined context structure passed when the DPC was queued.SystemArgument1
,SystemArgument2
: Optional arguments passed by the system.
Queuing a DPC
Drivers typically queue a DPC from their ISR or from another routine running at a high IRQL. The primary function for this is IoRequestDpc()
.
// Assuming 'pDpc' is a pointer to a initialized KDPC structure
// and 'pContext' is a pointer to driver-specific data
BOOLEAN
IoRequestDpc(
IN PVOID DeviceObject,
IN PIRP Irp OPTIONAL,
IN PKDPC Dpc,
IN PVOID Context OPTIONAL,
IN PVOID Arg1 OPTIONAL,
IN PVOID Arg2 OPTIONAL
);
If the DPC is already on the DPC queue, IoRequestDpc
returns FALSE
. If the DPC was successfully queued, it returns TRUE
.
Important Considerations:
- DPC routines must be non-blocking. They cannot wait for resources, call functions that might page out, or yield the processor.
- All operations within a DPC routine must be safe to execute at
DISPATCH_LEVEL
. - DPCs are executed serially by the system, meaning only one DPC runs at a time, even on multi-processor systems.
Note on Timers:
Timer objects are implemented using DPCs. When a timer expires, a DPC is automatically queued to execute the timer's callback routine.
DPC Object Initialization
Before a DPC can be queued, its associated _DPC
object must be initialized. This is typically done in the driver's AddDevice
routine or another initialization phase.
VOID
KeInitializeDpc(
IN PKDPC Dpc,
IN PKDEFERRED_ROUTINE DeferredRoutine,
IN PVOID Context
);
Advantages of DPCs
- Responsiveness: Allows ISRs to complete quickly, improving system responsiveness.
- Workload Balancing: Separates immediate interrupt handling from longer processing tasks.
- Efficient Resource Usage: Deferring tasks prevents holding critical system resources for extended periods.