天天看點

DPC分析 基于ReactOS0.33

    windows的過程,有如linux的軟中斷。以前​​Linux核心中自旋鎖同步分析​​提到過,Linux通過IN_HARDIRQ/IN_SOFTIRQ來屏蔽軟中斷的執行,windows其實也類似,偷梁換柱的用IRQL來代替這些概念:IRQL高于DPC_LEVEL時,DPC過程無法執行,因為可能是中斷過程正在執行;當通過KfLowerIrql降低IRQL請求級别到DPC_LEVEL及以下時開始執行DPC過程。降的再狠點,降到APC_LEVEL之下,還能執行APC請求。

    ReactOS中關于DPC的執行流程相比與其他子產品極其簡單,是以本文隻羅列大概流程,并不貼出代碼。首先關于DPC對象的産生,裝置對象初始化時可能會調用KiInitializeDpc,該函數僅僅設定執行DPC的過程和相應的參數,然後就結束了。裝置對象可能會把該Dpc對象儲存在自己的裝置擴充部中,供以後使用。以後是指什麼?當然是指發生中斷而進入中斷處理函數時,會調用KeInsertQueueDpc把Dpc對象插入到CPU控制子產品Prcb!DpcData隊列:

KDPC對象

typedef struct _KDPC
{
    UCHAR Type;
    UCHAR Importance;
    USHORT Number;
    LIST_ENTRY DpcListEntry;
    PKDEFERRED_ROUTINE DeferredRoutine;
    PVOID DeferredContext;
    PVOID SystemArgument1;
    PVOID SystemArgument2;
    //指向所挂入的KDPC_DATA結構,Prcb
    //上有兩個KDPC_DATA(DPC)請求隊列,一個是
    //普通的一個是DPC線程化的
    volatile PVOID  DpcData;
} KDPC, *PKDPC, *RESTRICTED_POINTER PRKDPC;      

KeInsertQueueDpc代碼片段:

KeRaiseIrql(HIGH_LEVEL, &OldIrql);
CurrentPrcb = KeGetCurrentPrcb();//獲得目前CPU的Prcb
...
/*
    從Prcb的兩個DpcData隊列中選一個,将請求的Dpc挂到其中
    */
    /* Check if this is a threaded DPC and threaded DPCs are enabled */
    if ((Dpc->Type == ThreadedDpcObject) && (Prcb->ThreadDpcEnable))
    {
        /* Then use the threaded data */
        DpcData = &Prcb->DpcData[DPC_THREADED];
    }
    else
    {
        /* Otherwise, use the regular data */
        DpcData = &Prcb->DpcData[DPC_NORMAL];
    }
...
KiAcquireSpinLock(&DpcData->DpcLock);//同個CPU的互斥,因為要插入隊列
..
Dpc->SystemArgument1 = SystemArgument1;
        Dpc->SystemArgument2 = SystemArgument2;
        DpcData->DpcQueueDepth++;
        DpcData->DpcCount++;
        DpcConfigured = TRUE;

        /* Check if this is a high importance DPC */
        if (Dpc->Importance == HighImportance)
        {
            /* Pre-empty other DPCs */
            InsertHeadList(&DpcData->DpcListHead, &Dpc->DpcListEntry);
        }
        else
        {
            /* Add it at the end */
            InsertTailList(&DpcData->DpcListHead, &Dpc->DpcListEntry);
        }
//插入隊列結束,釋放互斥鎖
KiReleaseSpinLock(&DpcData->DpcLock);
...
//最後發出DPC請求,當然DPC不會立馬得到執行
/*
            正式送出請求,等到cpu從高IRQL降到DPC以下時,
            核心掃描并執行隊列中的DPC函數,如處理完
            中斷後調用KfLowerIrql時,據說掃描并執行DPC隊列
            也是線程切換的時機,這麼說KiSwapContext是在DPC級别的
            切換完了,IRQL再降級,才有機會執行APC
            */
            HalRequestSoftwareInterrupt(DISPATCH_LEVEL);      

DPC的産生和請求都有了,那什麼時候才是投遞執行的時候?前面說了要等到IRQL下降,一般由調用者主動調用KfLowerIrql降低IRQL,借此機會DPC過程得以執行.

KfLowerIrql的内部實作是HalpLowerIrql,是以有必要進去浏覽一下:

VOID 
HalpLowerIrql(KIRQL NewIrql, BOOLEAN FromHalEndSystemInterrupt)
{
//執行Dpc/線程切換/Apc/的大好時機
  ULONG Flags;
  UCHAR DpcRequested;
  if (NewIrql >= DISPATCH_LEVEL)
    {
      KeSetCurrentIrql (NewIrql);
      APICWrite(APIC_TPR, IRQL2TPR (NewIrql) & APIC_TPR_PRI);
      return;
    }
  Ke386SaveFlags(Flags);
  if (KeGetCurrentIrql() > APC_LEVEL)
    {
      KeSetCurrentIrql (DISPATCH_LEVEL);
      APICWrite(APIC_TPR, IRQL2TPR (DISPATCH_LEVEL) & APIC_TPR_PRI);
      DpcRequested = __readfsbyte(FIELD_OFFSET(KIPCR, HalReserved[HAL_DPC_REQUEST]));
      if (FromHalEndSystemInterrupt || DpcRequested)
        {
          //以前在中斷進行中發出的Dpc請求,現在要關閉
          __writefsbyte(FIELD_OFFSET(KIPCR, HalReserved[HAL_DPC_REQUEST]), 0);
          _enable();
      //投遞Dpc
          KiDispatchInterrupt();
          if (!(Flags & EFLAGS_INTERRUPT_MASK))
            {
              _disable();
            }
  }
      KeSetCurrentIrql (APC_LEVEL);
    }
  if (NewIrql == APC_LEVEL)
    {
      return;
    }
  if (KeGetCurrentThread () != NULL && 
      KeGetCurrentThread ()->ApcState.KernelApcPending)
    {
      //投遞APC
      _enable();
      KiDeliverApc(KernelMode, NULL, NULL);
      if (!(Flags & EFLAGS_INTERRUPT_MASK))
        {
          _disable();
        }
    }
  KeSetCurrentIrql (PASSIVE_LEVEL);
}      

投遞Dpc還是在KiDispatchInterrupt中完成。這裡有個疑問,希望路過的大神解答就是:ReactOS進行中斷用了線程的堆棧,而處理Dpc使用Dpc專用堆棧,據說說linux中斷處理過程沒有程序的上下文,莫非linux中斷用的是tss中設定的esp0?

這個函數,除了執行dpc還有一個重要的作用,切換線程

call @KiRetireDpcList@4
...
call @KiSwapContextInternal@0      

繼續閱讀