windows内核原理分析之DPC函数的执行(3)

windows内核原理分析之DPC函数的执行(3)

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);  
} 

你可能感兴趣的:(windows内核)