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