windows核心原理分析之DPC函式的執行(1)

yushuifirst發表於2015-06-04

windows核心原理分析之DPC函式的執行(1)

當發生中斷時,有些操作本來應該在中斷服務程式中完成,但是實際上卻是在退出中斷服務程式之後在一個DPC函式中完成的。DPC是”Deferred Procedure Call”的縮寫,意為推遲了的過程(函式)呼叫。這是因為,邏輯上應該放在中斷服務程式中完成的操作並非都是那麼緊迫,其中有一部分可能相對而言不那麼緊迫,而又比較費時間,實際上可以放在開中斷的條件下執行。如果把這些操作都放在中斷服務程式中,就會使關閉中斷的時間太長而引起中斷請求的丟失,因為整個中斷服務程式通常都是在關中斷的條件下執行的。為此,把中斷服務程式中不那麼緊迫卻比較費時,而又不必在關中斷條件下執行的操作分割出來,放在另一個函式中,在開中斷的條件下加以執行,就可以縮短關中斷的時間。這樣的函式就是DPC函式。一般而言,中斷服務前期的操作是比較緊迫的,並且是必須關中斷的,此時可以很快地對外部裝置進行操作。此後,剩下的那部分操作便可以稍後在開中斷的條件下執行。所以有人曾經把這部分操作稱為中斷服務的”後半(Bottom Half)”,也有人把這兩半分別稱為”硬中斷”和”軟中斷”。之所以要把中斷服務分成前後兩半,是因為一次中斷服務的後半不如另一次中斷的前半那麼緊迫。

為此,核心中要有個DPC請求佇列,中斷服務程式執行完它的”前半”之後就把一個DPC請求掛入這個佇列,要求核心呼叫相應的DPC函式,然後(形式上)就從中斷返回了。接著,如果沒有別的中斷請求,核心就會掃描這個DPC請求佇列,依次在開中斷的條件下執行這些DPC函式,直至又發生中斷或執行完佇列中的所有DPC函式。至於當前執行緒所要執行的程式,則只有在DPC請求佇列為空的時候才會繼續得到執行。顯然,這裡所體現的是”急事急辦”的原則,中斷是最急的,DPC函式其次,最後才是當前執行緒。Windows核心的IRQL(即執行級別)就反映了這些活動的輕重緩急,DPC函式是在DISPATCH_LEVEL級別上執行的。

與DPC函式的執行有關的另一個問題是堆疊的使用。我們知道,中斷服務程式所使用的堆疊就是當前執行緒的系統空間堆疊。中斷服務程式一般都是比較輕小的,佔用一下當前執行緒的堆疊不至於會有問題;但是DPC函式就不同了,DPC函式有可能是比較大的,如果仍舊佔用當前執行緒的堆疊,在最壞的情況下有可能造成堆疊溢位,所以最好是為DPC函式的執行另外配備一個堆疊。

Windows核心把DPC請求佇列放在每個CPU的PRCB資料結構中。此外,PRCB結構中還有一些別的與DPC有關的欄位:

typedef struct _KPRCB  
{  
    ......  
    ULONG DpcTime;  
    ULONG DebugDpcTime;  
    ULONG InterruptTime;  
    ULONG AdjustDpcThreshold;  
    ......  
    struct _KDPC_DATA DpcData[2];        //兩個DPC請求佇列  
    PVOID DpcStack;  
    ULONG MaximumDpcQueueDepth;  
    ULONG DpcRequestRate;  
    ULONG MinimumDpcRate;  
    volatile UCHAR DpcInterruptRequested;  
    volatile UCHAR DpcThreadRequested;  
    volatile UCHAR DpcRoutineActive;  
    volatile UCHAR DpcThreadActive;  
    ULONG PrcbLock;  
    ULONG DpcLastCount;  
    ......  
    PVOID DpcThread;  
    KEVENT DpcEvent;  
    UCHAR ThreadDpcEnable;  
    ......  
    LONG DpcSetEventRequest;  
    ......  
    KDPC CallDpc;  
    ......  
} KPRCB, *PKPRCB; 

其中的DpcData[2]是大小為2的_KDPC_DATA結構陣列,其結構定義如下:

typedef struct _KDPC_DATA  
{  
    LIST_ENTRY DpcListHead;      //用於DPC請求佇列的佇列頭  
    ULONG DpcLock;  
    volatile ULONG DpcQueueDepth;  
    ULONG DpcCount;  
} KDPC_DATA, *PKDPC_DATA; 

可見,每個_KDPC_DATA結構中都有個佇列頭,這就是用於DPC請求佇列的。

DpcData[]的大小為2,說明有兩個DPC請求佇列,它們是:

#define DPC_NORMAL      0  
#define DPC_THREADED    1 

顯然,其中之一是”常規”的DPC請求佇列,另一個是”執行緒化”的DPC請求佇列。目前ReactOS已經實現的是常規DPC請求。

裝置物件的資料結構DEVICE_OBJECT中有個成分Dpc,這是個KDPC資料結構,用來設定有關本裝置物件的DPC函式的資訊:

typedef struct _KDPC  
{  
    UCHAR Type;                             //DpcObject或ThreadedDpcObject  
    UCHAR Importance;                       //緊迫程度  
    USHORT Number;                          //CPU號碼(在多處理器系統中)  
    LIST_ENTRY DpcListEntry;                //用來掛入DPC請求佇列  
    PKDEFERRED_ROUTINE DeferredRoutine;     //指向具體的DPC函式  
    PVOID DeferredContext;                  //執行DPC函式時的上下文  
    PVOID SystemArgument1;                  //執行DPC函式時的引數  
    PVOID SystemArgument2;  
    volatile PVOID  DpcData;                //指向所掛入的KDPC_DATA結構  
} KDPC, *PKDPC, *RESTRICTED_POINTER PRKDPC; 

裝置物件在初始化時通過KeInitializeDpc()設定好它的KDPC資料結構。

VOID NTAPI  
KeInitializeThreadedDpc(IN PKDPC Dpc, IN 
PKDEFERRED_ROUTINE DeferredRoutine,  
                      IN PVOID DeferredContext)  
{  
    /* Call the internal routine */  
    KiInitializeDpc(Dpc, DeferredRoutine, DeferredContext, 
ThreadedDpcObject);  
}  

VOID NTAPI  
KiInitializeDpc(IN PKDPC Dpc, IN PKDEFERRED_ROUTINE DeferredRoutine,  
              IN PVOID DeferredContext, IN KOBJECTS Type)  
{  
    /* Setup the DPC Object */  
    Dpc->TypeType = Type;  
    Dpc->Number = 0;  
    Dpc->Importance= MediumImportance;  
    Dpc->DeferredRoutineDeferredRoutine = DeferredRoutine;  
    Dpc->DeferredContextDeferredContext = DeferredContext;  
    Dpc->DpcData = NULL;  
} 

相關文章