DPC是什么?它的英文全称为Deferred Procedure Call,即延迟过程调用。它最初作用是设计为中断服务程序的一部分,用来解决中断服务处理时间过长的问题。因为每次触发中断,都会关中断,然后执行中断服务例程。由于关中断了,所以中断服务例程必须短小精悍,不能消耗过多时间,否则会导致系统丢失大量其他中断。但是有的中断,其中断服务例程要做的事情本来就很多,那怎么办?于是,可以在中断服务例程中先执行最紧迫的那部分工作,然后把剩余的相对来说不那么重要的工作移入到DPC函数中去执行。
每当触发一个中断时,中断服务例程可以在当前CPU中插入一个DPC,当执行完ISR,退出ISR后, CPU就会扫描它的DPC队列,依次执行里面的每个DPC,当执行完DPC后,才又回到当前线程的中断处继续执行。
既然提到ISR,那么它是什么?它的英文全称为Interrupt Service Routines,即中断服务处理。在Windows中如果有认为不太重要的操作会被打包成一个KDPC结构体,如下所示:
kd> dt _KDPC
ntdll!_KDPC
+0x000 Type : Int2B ;指明该结构体的类型,值为0x13
+0x002 Number : UChar ;指明属于哪个KPCR
+0x003 Importance : UChar ;优先级,取值0-2,0最低,2最高,优先级越高越优先执行,初始化默认值为1
+0x004 DpcListEntry : _LIST_ENTRY ;DPC链表,和进程线程链表一样,挂到腰上
+0x00c DeferredRoutine : Ptr32 void ;DPC的回调函数地址
+0x010 DeferredContext : Ptr32 Void ;回调函数上下文,非必须
+0x014 SystemArgument1 : Ptr32 Void ;回调函数的参数,非必须
+0x018 SystemArgument2 : Ptr32 Void ;回调函数的参数,非必须
+0x01c Lock : Ptr32 Uint4B DPC结构体的锁
打包完毕后,会插入到KPCRB的DpcListHead成员中,等待触发调用时机
kd> dt _KPRCB
ntdll!_KPRCB
……
+0x4b0 DpcTime : Uint4B
+0x4b4 DebugDpcTime : Uint4B
……
+0x860 DpcListHead : _LIST_ENTRY
+0x868 DpcStack : Ptr32 Void
+0x86c DpcCount : Uint4B
+0x870 DpcQueueDepth : Uint4B
+0x874 DpcRoutineActive : Uint4B
+0x878 DpcInterruptRequested : Uint4B
+0x87c DpcLastCount : Uint4B
+0x880 DpcRequestRate : Uint4B
……
+0x8a0 DpcLock : Uint4B
……
+0x8c0 CallDpc : _KDPC
……
用户或者驱动创建的DPC,还可以加定时器,到指定时间进行触发,但不会挂到KPCR当中,会挂到时钟任务当中
DPC初始化
; void __stdcall KeInitializeDpc(PRKDPC Dpc, PKDEFERRED_ROUTINE DeferredRoutine, PVOID DeferredContext)
public _KeInitializeDpc@12
_KeInitializeDpc@12 proc near ; CODE XREF: IopInitializeIrpStackProfiler()+29↑p
; VdmpDelayInterrupt(x)+26B↓p ...
Dpc = dword ptr 8
DeferredRoutine = dword ptr 0Ch
DeferredContext = dword ptr 10h
mov edi, edi
push ebp
mov ebp, esp
mov eax, [ebp+Dpc]
mov ecx, [ebp+DeferredRoutine]
and dword ptr [eax+1Ch], 0
mov [eax+0Ch], ecx
mov ecx, [ebp+DeferredContext]
mov word ptr [eax], 13h
mov byte ptr [eax+2], 0
mov byte ptr [eax+3], 1
mov [eax+10h], ecx
pop ebp
retn 0Ch
_KeInitializeDpc@12 endp
DPC插入
DPC的优先级高,就会插到DPC链表的前面,如果低就会插到末尾
BOOLEAN __stdcall KeInsertQueueDpc(PRKDPC Dpc, PVOID SystemArgument1, PVOID SystemArgument2)
{
_KPRCB *kprcb; // esi
bool Importance; // zf
_LIST_ENTRY *DpcListHead; // ecx
_LIST_ENTRY *DpcListEntry; // eax
_LIST_ENTRY *v9; // edx
_LIST_ENTRY *v10; // edx
KIRQL NewIrql; // [esp+Fh] [ebp-1h]
NewIrql = KfRaiseIrql(0x1Fu);
kprcb = MEMORY[0xFFDFF020];
_ECX = &Dpc->Lock;
_EDX = MEMORY[0xFFDFF020] + 0x8A0; // DpcLock
__asm { cmpxchg [ecx], edx }
++kprcb->DpcCount;
++kprcb->DpcQueueDepth;
Importance = Dpc->Importance == 2;
Dpc->SystemArgument1 = SystemArgument1;
Dpc->SystemArgument2 = SystemArgument2;
DpcListHead = &kprcb->DpcListHead;
DpcListEntry = &Dpc->DpcListEntry;
if ( Importance )
{
v9 = DpcListHead->Flink;
DpcListEntry->Flink = DpcListHead->Flink;
Dpc->DpcListEntry.Blink = DpcListHead;
v9->Blink = DpcListEntry;
DpcListHead->Flink = DpcListEntry;
}
else
{
v10 = kprcb->DpcListHead.Blink;
DpcListEntry->Flink = DpcListHead;
Dpc->DpcListEntry.Blink = v10;
v10->Flink = DpcListEntry;
kprcb->DpcListHead.Blink = DpcListEntry;
}
if ( !kprcb->DpcRoutineActive
&& !kprcb->DpcInterruptRequested
&& (Dpc->Importance
|| kprcb->DpcQueueDepth >= kprcb->MaximumDpcQueueDepth
|| kprcb->DpcRequestRate < kprcb->MinimumDpcRate) )
{
LOBYTE(DpcListHead) = 2;
kprcb->DpcInterruptRequested = 1;
HalRequestSoftwareInterrupt(DpcListHead);
}
KfLowerIrql(NewIrql);
return 1;
}