windows内核什么时候会扫描DPC请求队列,执行这些DPC函数呢?答案是,每当CPU的运行级别从DISPATCH_LEVEL或以上降低到DISPATCH_LEVEL以下时,如果有扫描DPC请求队列的要求存在,内核就会扫描DPC请求队列并执行DPC函数(如果队列非空的话)。为此,我们不妨从宏操作KeLowerIrql()开始往下看。
#define KeLowerIrql(a) KfLowerIrql(a)
VOID FASTCALL KfLowerIrql (KIRQLNewIrql)
{
if (NewIrql > KeGetPcr()->Irql)
{
KEBUGCHECK(0);
for(;;);
}
HalpLowerIrql(NewIrql);
}
VOID HalpLowerIrql(KIRQL NewIrql) //主要函数
{
if (NewIrql >= PROFILE_LEVEL) //如果所要降到的中断请求级大于PROFILE_LEVEL,则直接设置当前的中断请求级
{
KeGetPcr()->Irql = NewIrql;
return;
}
HalpExecuteIrqs(NewIrql);
if (NewIrql >= DISPATCH_LEVEL) //如果所要降到的中断请求级大于DISPATCH_LEVEL,则直接设置当前的中断请求级
{
KeGetPcr()->Irql = NewIrql;
return;
}
//NewIrql低于DISPATCH_LEVEL
KeGetPcr()->Irql = DISPATCH_LEVEL; //所要降到的中断请求级小于DISPATCH_LEVEL,设置当前的中断请求级为DISPATCH_LEVEL,
//然后扫描dpc队列,如果不为空,则触发dpc软件中断
if (((PKIPCR)KeGetPcr())->HalReserved[HAL_DPC_REQUEST])
{ //DPC请求队列非空
((PKIPCR)KeGetPcr())->HalReserved[HAL_DPC_REQUEST] = FALSE;
KiDispatchInterrupt();
}
KeGetPcr()->Irql = APC_LEVEL; //所要降到的中断请求级小于APC_LEVEL,设置当前的中断请求级为APC_LEVEL,
//然后扫描apc队列,如果不为空,则触发apc软件中断
if (NewIrql == APC_LEVEL)
{
return;
}
//NewIrql低于APC_LEVEL
if (KeGetCurrentThread() != NULL &&
KeGetCurrentThread()->ApcState.KernelApc Pending)
{
KiDeliverApc(KernelMode, NULL, NULL);
}
KeGetPcr()->Irql = PASSIVE_LEVEL;
}
退出中断服务程序的时候当然要降低CPU的运行级别,但这并不是唯一的原因,我们从内核的代码中看到,调用KeLowerIrql()的地方还是不少的。
从HalpLowerIrql()的代码中可以看出,如果原来的运行级别高,而现在降到了DISPATCH_LEVEL以下,并发现有扫描DPC请求队列的要求存在,就会调用一个函数KiDispatchInterrupt(),这是一个汇编语言函数:
[KfLowerIrql() > HalpLowerIrql() > KiDispatchInterrupt()]
_KiDispatchInterrupt@0:
/* Get the PCR and disable interrupts */
mov ebx, PCR[KPCR_SELF]
cli //关中断
/* Check if we have to deliver DPCs, timers, or deferred threads */
mov eax, [ebx+KPCR_PRCB_DPC_QUEUE_DEPTH]
or eax, [ebx+KPCR_PRCB_TIMER_REQUEST]
or eax, [ebx+KPCR_PRCB_DEFERRED_READY_LIST_HEAD]
jz CheckQuantum //没有任何DPC请求,DPC请求队列空
/* Save stack pointer and exception list, then clear it */
push ebp
push dword ptr [ebx+KPCR_EXCEPTION_LIST]
mov dword ptr [ebx+KPCR_EXCEPTION_LIST], -1
/* Save the stack and switch to the DPC Stack */
mov edx, esp
mov esp, [ebx+KPCR_PRCB_DPC_STACK] //暂时切换到DPC堆栈
push edx
/* Deliver DPCs */
mov ecx, [ebx+KPCR_PRCB]
call @KiRetireDpcList@4 //调用从KiRetireDpcList函数
/* Restore stack and exception list */
pop esp //恢复堆栈
pop dword ptr [ebx+KPCR_EXCEPTION_LIST]
pop ebp
CheckQuantum:
cmp byte ptr [ebx+KPCR_PRCB_QUANTUM_END], 0 //现在回头检查当前线程的时间片是否耗尽
jnz QuantumEnd //若已耗尽,直接跳到QuantumEnd处执行线程切换
cmp byte ptr [ebx+KPCR_PRCB_NEXT_THREAD], 0 //再检查当前是否有一个抢占者线程
je Return
/* Make space on the stack to save registers */
sub esp, 3 * 4
mov [esp+8], esi //保存
mov [esp+4], edi //保存
mov [esp+0], ebp //保存
mov edi, [ebx+KPCR_CURRENT_THREAD]
#ifdef CONFIG_SMP //if多处理器
call _KeRaiseIrqlToSynchLevel@0 //提升irql到SynchLevel
mov byte ptr [edi+KTHREAD_SWAP_BUSY], 1 //标记该线程正在进行切换
/* Acquire the PRCB Lock */
lock bts dword ptr [ebx+KPCR_PRCB_PRCB_LOCK], 0
jnb GetNext
lea ecx, [ebx+KPCR_PRCB_PRCB_LOCK]
call @KefAcquireSpinLockAtDpcLevel@4
#endif
GetNext:
mov esi, [ebx+KPCR_PRCB_NEXT_THREAD]
and dword ptr [ebx+KPCR_PRCB_NEXT_THREAD], 0
mov [ebx+KPCR_CURRENT_THREAD], esi
mov byte ptr [esi+KTHREAD_STATE_], Running
mov byte ptr [edi+KTHREAD_WAIT_REASON], WrDispatchInt
mov ecx, edi
lea edx, [ebx+KPCR_PRCB_DATA]
call @KiQueueReadyThread@8
mov cl, APC_LEVEL
call @KiSwapContextInternal@0
#ifdef CONFIG_SMP
mov cl, DISPATCH_LEVEL
call @KfLowerIrql@4
#endif
mov ebp, [esp+0] //恢复
mov edi, [esp+4] //恢复
mov esi, [esp+8] //恢复
add esp, 3*4
Return:
pop ebx
ret
QuantumEnd:
mov byte ptr [ebx+KPCR_PRCB_QUANTUM_END], 0
call _KiQuantumEnd@0 //调用这个函数切换线程
pop ebx
ret
扫描执行DPC请求队列的时机也是线程切换的时机,但是在这里我们只关心DPC请求。对DPC请求队列的扫描和执行实际上是由KiRetireDpcList()完成的,但是这里在调用这个函数之前切换到了另一个堆栈,从KiRetireDpcList()返回之后再予恢复。这就是为了让执行DPC函数的过程使用另一个堆栈即DPC专用堆栈,以免当前线程的系统空间堆栈溢出。
在扫描dpc队列执行完所有dpc函数后,会检查当前线程的时间片是否耗尽,若耗尽就进行线程切换,若尚未耗尽,就检查当前是否有一个抢占者线程,若有,也进行线程切换。
【总之:系统在每次扫描执行完dpc队列后,都会尝试进行线程切换】不像APC的执行时机有很多,DPC的执行时机就一处。
前面讲过,DPC函数是在开中断的条件下执行的,在这里我们却看到对KiRetireDpcList()的调用是在关中断的条件下执行。但是KiRetireDpcList()并不等于DPC函数本身,下面我们就将看到,对具体DPC函数的调用确实是在开中断的条件下进行的。
[KfLowerIrql() > HalpLowerIrql() >
KiDispatchInterrupt() > KiRetireDpcList()]
VOID FASTCALL KiRetireDpcList(IN PKPRCB Prcb)
{
......
/* Get data and list variables before starting anything else */
DpcData = &Prcb->DpcData[DPC_NORMAL];
ListHead = &DpcData->DpcListHead;
/* Main outer loop */
do
{
/* Set us as active */
Prcb->DpcRoutineActive = TRUE;
/* Check if this is a timer expiration request */
if (Prcb->TimerRequest)
{ //定时器的处理是一种特殊的DPC
/* It is, get the timer hand and disable timer request */
TimerHand = Prcb->TimerHand;
Prcb->TimerRequest = 0;
/* Expire timers with interrups enabled */
_enable(); //开中断
KiTimerExpiration(NULL, NULL, (PVOID)TimerHand, NULL);
_disable(); //关中断
}
/* Loop while we have entries in the queue */
while (DpcData->DpcQueueDepth != 0)
{ //对于DPC请求队列中的每一项
/* Lock the DPC data and get the DPC entry*/
KefAcquireSpinLockAtDpcLevel(&DpcData->DpcLock);
DpcEntry = ListHead->Flink;
/* Make sure we have an entry */
if (DpcEntry != ListHead)
{
/* Remove the DPC from the list */
RemoveEntryList(DpcEntry);
Dpc = CONTAINING_RECORD(DpcEntry, KDPC, DpcListEntry);
/* Clear its DPC data and save its parameters */
Dpc->DpcData = NULL;
DeferredRoutine = Dpc->DeferredRoutine;
//具体的DPC函数指针
DeferredContext = Dpc->DeferredContext;
SystemArgument1 = Dpc->SystemArgument1;
SystemArgument2 = Dpc->SystemArgument2;
/* Decrease the queue depth */
DpcData->DpcQueueDepth--;
/* Clear DPC Time */
Prcb->DebugDpcTime = 0;
/* Release the lock */
KefReleaseSpinLockFromDpcLevel(&DpcData->DpcLock);
/* Re-enable interrupts */
_enable(); //开中断
/* Call the DPC */
DeferredRoutine(Dpc, DeferredContext,
SystemArgument1,
SystemArgument2); //执行DPC函数
ASSERT(KeGetCurrentIrql() == DISPATCH_LEVEL);
/* Disable interrupts and keep looping */
_disable(); //关中断
}
else
{
/* The queue should be flushed now */
ASSERT(DpcData->DpcQueueDepth == 0);
/* Release DPC Lock */
KefReleaseSpinLockFromDpcLevel(&DpcData->DpcLock);
}
}
/* Clear DPC Flags */
Prcb->DpcRoutineActive = FALSE;
Prcb->DpcInterruptRequested = FALSE;
/* Check if we have deferred threads */
if (Prcb->DeferredReadyListHead.Next)
{
/* FIXME: 2K3-style scheduling not implemeted */
ASSERT(FALSE);
}
} while (DpcData->DpcQueueDepth != 0);
}