VOID
IoInitializeDpcRequest(
IN PDEVICE_OBJECT DeviceObject,
IN PIO_DPC_ROUTINE DpcRoutine)
{
KeInitializeDpc( &DeviceObject->Dpc,
(PKDEFERRED_ROUTINE) DpcRoutine,
DeviceObject );
}
APC更多是和对应的线程关联的,而DPC则是和设备对象关联的。设备对象本身包含一个DPC结构体,如果在驱动开发过程中需要DPC支持,则可以通过将设备对象的DPC指针进行初始化,并填充相应的DPC处理例程。正因为DPC是在驱动程序中使用的,所以它的运行上下文是不确定的。也正因为这个原因,所以DPC的运行需要在DISPATCH_LEVEL级别进行,同时也就要求DPC运行的时候所涉及到的数据必须是非换页的。
VOID
NTAPI
KeInitializeDpc(IN PKDPC Dpc,
IN PKDEFERRED_ROUTINE DeferredRoutine,
IN PVOID DeferredContext)
{
KiInitializeDpc(Dpc, DeferredRoutine, DeferredContext, DpcObject);
}
VOID
NTAPI
KeInitializeThreadedDpc(IN PKDPC Dpc,
IN PKDEFERRED_ROUTINE DeferredRoutine,
IN PVOID DeferredContext)
{
KiInitializeDpc(Dpc, DeferredRoutine, DeferredContext, ThreadedDpcObject);
}
上面的两个函数很简单,只是根据不同类型的DPC调用内部的实现函数。不过需要注意的是,后者虽然带thread,但是这种DPC的运行上下文同样也是任意的。这一点可以从下面的代码中得到佐证。内部函数的实现很简单,只是根据传递的信息进行结构体的成员变量填充而已。其中有一个很关键的数据变量DpcData填充为NULL。DpcData的原型定义如下所示:
typedef struct _KDPC_DATA
{
LIST_ENTRY DpcListHead;
ULONG_PTR DpcLock;
volatile ULONG DpcQueueDepth;
ULONG DpcCount;
} KDPC_DATA, *PKDPC_DATA;
其中,DpcData会根据DPC的CPU亲和性找到对应的PRCB,根据这个PRCB结构体中的KDPC_DATA数据结构指针,填充DpcData。其中DpcListHead作为整个DPC的链表表头指针存在,而DpcLock则作为访问DpcData的互斥锁。DpcQueueDepth为队列中包含的DPC的个数,而DpcCount则用于统计DPC的综述,作为性能分析之用。
BOOLEAN
NTAPI
KeInsertQueueDpc(IN PKDPC Dpc,
IN PVOID SystemArgument1,
IN PVOID SystemArgument2)
{
KIRQL OldIrql;
PKPRCB Prcb, CurrentPrcb;
ULONG Cpu;
PKDPC_DATA DpcData;
BOOLEAN DpcConfigured = FALSE, DpcInserted = FALSE;
KeRaiseIrql(HIGH_LEVEL, &O