[9]Windows内核情景分析 --- DPC
DPC不同APC,DPC的全名是‘延迟过程调用’。
DPC最初作用是设计为中断服务程序的一部分。因为每次触发中断,都会关中断,然后执行中断服务例程。由于关中断了,所以中断服务例程必须短小精悍,不能消耗过多时间,否则会导致系统丢失大量其他中断。但是有的中断,其中断服务例程要做的事情本来就很多,那怎么办?于是,可以在中断服务例程中先执行最紧迫的那部分工作,然后把剩余的相对来说不那么重要的工作移入到DPC函数中去执行。因此,DPC又叫ISR的后半部。(比如每次时钟中断后,其isr会扫描系统中的所有定时器是否到点,若到点就调用各定时器的函数。但是这个扫描过程比较耗时,因此,时钟中断的isr会将扫描工作纳入dpc中进行)
每当触发一个中断时,中断服务例程可以在当前cpu中插入一个DPC,当执行完isr,退出isr后, cpu就会扫描它的dpc队列,依次执行里面的每个dpc,当执行完dpc后,才又回到当前线程的中断处继续执行。
typedef struct _KDPC {
UCHAR Type; //DPC类型(分为普通DPC和线程化DPC)
UCHAR Importance;//该DPC的重要性,将决定挂在队列头还是尾
volatile USHORT Number;//第5位为0就表示当前cpu,否则,最低4位表示目标cpu号
LIST_ENTRY DpcListEntry;//用来挂入dpc链表
PKDEFERRED_ROUTINE DeferredRoutine;//dpc函数
PVOID DeferredContext;//dpc函数的参数
PVOID SystemArgument1;//挂入时的系统附加参数1
PVOID SystemArgument2;//挂入时的系统附加参数2
volatile PVOID DpcData;//所在的dpc队列
} KDPC, *PKDPC, *RESTRICTED_POINTER PRKDPC;
VOID
KeInitializeDpc(IN PKDPC Dpc,//DPC对象(DPC也是一种内核对象)
IN PKDEFERRED_ROUTINE DeferredRoutine, //DPC函数
IN PVOID DeferredContext)//DPC函数的参数
{
KiInitializeDpc(Dpc, DeferredRoutine, DeferredContext, DpcObject);
}
VOID
KiInitializeDpc(IN PKDPC Dpc,
IN PKDEFERRED_ROUTINE DeferredRoutine,
IN PVOID DeferredContext,
IN KOBJECTS Type)
{
Dpc->Type = Type;
Dpc->Number = 0;//初始的目标cpu为当前cpu
Dpc->Importance= MediumImportance;
Dpc->DeferredRoutine = DeferredRoutine;
Dpc->DeferredContext = DeferredContext;
Dpc->DpcData = NULL;//表示该DPC尚未挂入任何DPC队列
}
DPC初始化构造时的目标cpu默认都是当前cpu。
BOOLEAN KeInsertQueueDpc(IN PKDPC Dpc,IN PVOID SystemArgument1,IN PVOID SystemArgument2)
{
KIRQL OldIrql;
PKPRCB Prcb, CurrentPrcb;
ULONG Cpu;
PKDPC_DATA DpcData;
BOOLEAN DpcConfigured = FALSE, DpcInserted = FALSE;
KeRaiseIrql(HIGH_LEVEL, &OldIrql);//插入过程的中断级是最高的,这个过程不会被打断。
CurrentPrcb = KeGetCurrentPrcb();
//检查目标cpu号的第5位为1(32 = 00100000),就表示其它cpu,低4位表示cpu号
if (Dpc->Number >= 32)
{
Cpu = Dpc->Number - 32;
Prcb = KiProcessorBlock[Cpu];
}
Else //否则,表示当前cpu
{
Cpu = Prcb->Number;
Prcb = CurrentPrcb;//目标cpu就是当前cpu
}
//if 要插入的是一个线程化dpc并且那个cpu启用了线程化dpc机制
if ((Dpc->Type == ThreadedDpcObject) && (Prcb->ThreadDpcEnable))
DpcData = &Prcb->DpcData[DPC_THREADED]; //目标cpu的线程化dpc队列
else
DpcData = &Prcb->DpcData[DPC_NORMAL];//目标cpu的普通dpc队列
KiAcquireSpinLock(&DpcData->DpcLock);
//if 尚未挂入任何dpc队列
if (!InterlockedCompareExchangePointer(&Dpc->DpcData, DpcData, NULL))
{
Dpc->SystemArgument1 = SystemArgument1;
Dpc->SystemArgument2 = SystemArgument2;
DpcData->DpcQueueDepth++;
DpcData->DpcCount++;
DpcConfigured = TRUE;
//不管如何,先把dpc挂到目标cpu的dpc队列中
if (Dpc->Importance == HighImportance)
InsertHeadList(&DpcData->DpcListHead, &Dpc->DpcListEntry);
else
InsertTailList(&DpcData->DpcListHead, &Dpc->DpcListEntry);
if (&Prcb->DpcData[DPC_THREADED] == DpcData)
{
if (!(Prcb->DpcThreadActive) && !(Prcb->DpcThreadRequested))
线程化DPC,ReactOS目前尚不支持,略
}
Else //若挂入的是一个普通dpc(最常见),检查是否需要立即发出一个dpc软中断给cpu
{
//if 目标cpu当前没在执行dpc,并且它尚未收到dpc中断请求
if (!(Prcb->DpcRoutineActive) && !(Prcb->DpcInterruptRequested))
{
if (Prcb != CurrentPrcb)//if 目标cpu是其它cpu
{
if (((Dpc->Importance == HighImportance) ||
(DpcData->DpcQueueDepth >= Prcb->MaximumDpcQueueDepth))
&&
(!(AFFINITY_MASK(Cpu) & KiIdleSummary) || (Prcb->Sleeping)))
{
Prcb->DpcInterruptRequested = TRUE;
DpcInserted = TRUE; //表示需要立即给cpu发出dpc软中断
}
}
Else //if 目标cpu就是自身cpu,最常见
{
//一般插入的都是中等重要性的dpc,因此,一般会立即发出一个dpc中断。
if ((Dpc->Importance != LowImportance) ||
(DpcData->DpcQueueDepth >= Prcb->MaximumDpcQueueDepth) ||
(Prcb->DpcRequestRate < Prcb->MinimumDpcRate))
{
Prcb->DpcInterruptRequested = TRUE;
DpcInserted = TRUE; //表示需要立即给cpu发出dpc软中断
}
}
}
}
}
KiReleaseSpinLock(&DpcData->DpcLock);
if (DpcInserted)//if 需要立即发出一个dpc软中断
{
if (Prcb != CurrentPrcb)
KiIpiSend(AFFINITY_MASK(Cpu), IPI_DPC);
else
HalRequestSoftwareInterrupt(DISPATCH_LEVEL);//给当前cpu发出一个dpc软中断
}
KeLowerIrql(OldIrql);//降低irql
return DpcConfigured;
}
如上,这个函数将dpc挂入目标cpu的指定dpc队列(每个cpu有两个dpc队列,一个普通的,一个线程化的)。然后检查当前是否可以立即向目标cpu发出一个dpc软中断,这样,以在下次降低到DISPATCH_LEVEL以下时扫描执行dpc(其实上面的这个函数一般用于在isr中调用,但用户也可以随时手动调用)
(一般来说,挂入的都是中等重要性的dpc,一般在dpc进队的同时就会顺势发出一个dpc中断.)
下面的函数可用于模拟硬件,向cpu发出任意irql级别的软中断,请求cpu处理执行那种中断。
VOID FASTCALL
HalRequestSoftwareInterrupt(IN KIRQL Irql)//Irql一般是APC_LEVEL/DPC_LEVEL
{
ULONG EFlags;
PKPCR Pcr = KeGetPcr();
KIRQL PendingIrql;
EFlags = __readeflags();//保存老的eflags寄存器
_disable();//关中断
Pcr->IRR |= (1 << Irql);//关键。标志向cpu发出了一个对应irql级的软中断
PendingIrql = SWInterruptLookUpTable[Pcr->IRR & 3];//IRR后两位表示是否有阻塞的apc中断
//若有阻塞的apc中断,并且当前irql是PASSIVE_LEVEL,立即执行apc。也即在PASSIVE_LEVEL级时发出任意软中断后,会立即检查执行现有的apc中断。
if (PendingIrql > Pcr->Irql)
SWInterruptHandlerTable[PendingIrql]();//调用执行apc中断的isr,处理apc中断
__writeeflags(EFlags);//恢复原eflags寄存器
}
DPC函数的执行时机:
我们看一个函数
VOID FASTCALL
KfLowerIrql(IN KIRQL OldIrql)//降回到原irql
{
ULONG EFlags;
ULONG PendingIrql, PendingIrqlMask;
PKPCR Pcr = KeGetPcr();
PIC_MASK Mask;
EFlags = __readeflags();//保存老的eflags寄存器
_disable();//关中断
Pcr->Irql = OldIrql;//降低到指定irql
//检查是否有在目标irql以上的阻塞中的软中断
PendingIrqlMask = Pcr->IRR & FindHigherIrqlMask[OldIrql];
if (PendingIrqlMask)
{
//从高位到低位扫描,找到阻塞中的最高irql级的软中断
BitScanReverse(&PendingIrql, PendingIrqlMask);
if (PendingIrql > DISPATCH_LEVEL) …
SWInterruptHandlerTable[PendingIrql]();//处理那个软中断,似乎这儿有问题,应该是
SWInterruptHandlerTable[31-PendingIrql]();
}
__writeeflags(EFlags);//恢复原eflags寄存器
}
unsigned char BitScanReverse(ULONG * const Index, unsigned long Mask)
{
*Index = 0;
while (Mask && ((Mask & (1 << 31)) == 0))
{
Mask <<= 1;
++(*Index);
}
return Mask ? 1 : 0;
}
各个软中断的处理函数如下:(怀疑这个表的布局有问题)
PHAL_SW_INTERRUPT_HANDLER SWInterruptHandlerTable[20] =
{
KiUnexpectedInterrupt,//PASSIVE_LEVEL最低了,永远不会中断别的irql
HalpApcInterrupt,//APC中断的isr
HalpDispatchInterrupt2,//DPC中断的isr
KiUnexpectedInterrupt,
HalpHardwareInterrupt0,
HalpHardwareInterrupt1,
HalpHardwareInterrupt2,
HalpHardwareInterrupt3,
HalpHardwareInterrupt4,
HalpHardwareInterrupt5,
HalpHardwareInterrupt6,
HalpHardwareInterrupt7,
HalpHardwareInterrupt8,
HalpHardwareInterrupt9,
HalpHardwareInterrupt10,
HalpHardwareInterrupt11,
HalpHardwareInterrupt12,
HalpHardwareInterrupt13,
HalpHardwareInterrupt14,
HalpHardwareInterrupt15
};
下面是处理DPC软中断的isr
VOID HalpDispatchInterrupt2(VOID)
{
ULONG PendingIrqlMask, PendingIrql;
KIRQL OldIrql;
PIC_MASK Mask;
PKPCR Pcr = KeGetPcr();
//这个函数里面会提高irql到DISPACH_LEVEL去扫描执行dpc队列中的所有dpc
OldIrql = _HalpDispatchInterruptHandler();//关键函数
Pcr->Irql = OldIrql;//恢复成原来的irql
//再去检测是否仍有更高irql的阻塞软中断
PendingIrqlMask = Pcr->IRR & FindHigherIrqlMask[OldIrql];
if (PendingIrqlMask)
{
BitScanReverse(&PendingIrql, PendingIrqlMask);
if (PendingIrql > DISPATCH_LEVEL) …
SWInterruptHandlerTable[PendingIrql]();//应该是[31 - PendingIrql]
}
}
KIRQL _HalpDispatchInterruptHandler(VOID)
{
KIRQL CurrentIrql;
PKPCR Pcr = KeGetPcr();
CurrentIrql = Pcr->Irql;
Pcr->Irql = DISPATCH_LEVEL;//将irql临时提高到DISPATCH_LEVEL
Pcr->IRR &= ~(1 << DISPATCH_LEVEL);//清除对应的软中断位
_enable();//开中断
KiDispatchInterrupt();//关键函数。开中断后扫描执行所有dpc
_disable();
return CurrentIrql;//返回原irql
}
//下面的函数扫描当前cpu的dpc队列执行所有dpc
KiDispatchInterrupt
{
push ebx
mov ebx, PCR[KPCR_SELF] //ebc = kpcr*
cli //关中断
//检查dpc队列是否为空
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
push ebp //保存
push dword ptr [ebx+KPCR_EXCEPTION_LIST] //保存原seh
mov dword ptr [ebx+KPCR_EXCEPTION_LIST], -1 //将当前色seh置空
/* Save the stack and switch to the DPC Stack */
mov edx, esp
mov esp, [ebx+KPCR_PRCB_DPC_STACK] //切换为DPC函数专用的内核栈
push edx //保存原来的内核栈顶位置
mov ecx, [ebx+KPCR_PRCB]
call @KiRetireDpcList@4 //关键。扫描执行dpc
pop esp //恢复成原来的内核栈顶
pop dword ptr [ebx+KPCR_EXCEPTION_LIST] //恢复
pop ebp //恢复
CheckQuantum:
Sti //开中断
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函数运行在任意线程的上下文中,而dpc函数可能太大,局部变量太多而占用了过多的内核栈,所以需要为dpc函数的执行专门配备一个栈。
这个函数还有一个注意地方,就是在扫描dpc队列执行完所有dpc函数后,会检查当前线程的时间片是否耗尽,若耗尽就进行线程切换,若尚未耗尽,就检查当前是否有一个抢占者线程,若有,也进行线程切换。
【总之:系统在每次扫描执行完dpc队列后,都会尝试进行线程切换】
下面的函数才是最终扫描执行dpc的
VOID FASTCALL
KiRetireDpcList(IN PKPRCB Prcb)
{
PKDPC_DATA DpcData;
PLIST_ENTRY ListHead, DpcEntry;
PKDPC Dpc;
PKDEFERRED_ROUTINE DeferredRoutine;
PVOID DeferredContext, SystemArgument1, SystemArgument2;
ULONG_PTR TimerHand;
DpcData = &Prcb->DpcData[DPC_NORMAL];//当前cpu的普通dpc队列
ListHead = &DpcData->DpcListHead;
do
{
Prcb->DpcRoutineActive = TRUE;//标记当前cpu正在执行dpc
if (Prcb->TimerRequest) //if收到有定时器到期dpc中断(定时器是一种特殊的dpc)
{
TimerHand = Prcb->TimerHand;
Prcb->TimerRequest = 0;
_enable();
KiTimerExpiration(NULL, NULL, (PVOID)TimerHand, NULL);//处理定时器队列
_disable();
}
while (DpcData->DpcQueueDepth != 0)//遍历dpc队列
{
KeAcquireSpinLockAtDpcLevel(&DpcData->DpcLock);
DpcEntry = ListHead->Flink;
if (DpcEntry != ListHead)
{
RemoveEntryList(DpcEntry);//取下来
Dpc = CONTAINING_RECORD(DpcEntry, KDPC, DpcListEntry);
Dpc->DpcData = NULL;
DeferredRoutine = Dpc->DeferredRoutine;
DeferredContext = Dpc->DeferredContext;
SystemArgument1 = Dpc->SystemArgument1;
SystemArgument2 = Dpc->SystemArgument2;
DpcData->DpcQueueDepth--;
Prcb->DebugDpcTime = 0;
KeReleaseSpinLockFromDpcLevel(&DpcData->DpcLock);
_enable();//开中断
//关键。执行DPC例程
DeferredRoutine(Dpc,DeferredContext,SystemArgument1,SystemArgument2);
ASSERT(KeGetCurrentIrql() == DISPATCH_LEVEL);
_disable();//关中断
}
else
{
ASSERT(DpcData->DpcQueueDepth == 0);//肯定执行完了所有dpc
KeReleaseSpinLockFromDpcLevel(&DpcData->DpcLock);
}
}
Prcb->DpcRoutineActive = FALSE;//当前不再用dpc正在执行
Prcb->DpcInterruptRequested = FALSE;
} while (DpcData->DpcQueueDepth != 0);
}
注意:DPC函数运行在DISPATCH_LEVEL,并且开中断,因此dpc函数本身又可能被其他中断给中断。
因为dpc函数本身就是一种软中断,因此它支持中断嵌套。
综上:
DPC作为一种软中断,也是在irql的降低过程中得到执行的,并且是在从DISPATCH_LEVEL以上(不包括)降低到DISPATCH_LEVEL以下(不包括),也即在穿越DISPATCH_LEVEL的过程中,系统会先暂停在DISPATCH_LEVEL级执行当前cpu的dpc队列中所有阻塞的dpc,执行完后,再降回到真正的irql。
【总之:在降低过程中检查是否有dpc中断,若有执行之】一句口诀:【降低、检断、DPC】
不像APC的执行时机有很多,DPC的执行时机就一处。
那么在什么时候,系统会降低irql呢?除了用户显式调用这个内核函数外,isr一般工作在比DISPATCH_LEVEL高的irql,当isr退出时,必然会降低irql到原来的irql。因此,常常在isr中插入一个dpc到dpc队列,发出一个dpc中断给cpu,然后退出isr时,降低irql,顺理成章的执行dpc。
KeInitializeDpc初始化的dpc,默认的目标cpu都是当前cpu,如果需要将dpc发给其他cpu,让其在其他cpu上运行的话,可以采用下面的函数
VOID
KeSetTargetProcessorDpc(IN PKDPC Dpc,IN CCHAR Number)
{
Dpc->Number = Number + 32;
}
这是一个非常有用的函数,因为他可以使你的代码运行在你想要的cpu上。比如,你写了一个函数,你只想那个函数运行在3号cpu上,那么,你可以构造一个在3号cpu上运行的dpc,然后在dpc里调用你自己的函数。这种技术常用于保障内联hook的多cpu线程安全 和 IDT hook。
当然也可使用KeSetSystemAffinityThread这个内核函数,修改当前线程的cpu亲缘性为‘只能运行在目标cpu’上,这样,也会立即导致当前线程立刻挪到其它cpu上去运行,KeSetSystemAffinityThread的代码,有兴趣的读者自己看。
本篇既然谈到了DPC,那就要讲下与之紧密相关的另一个话题:‘系统工作者线程’。
DPC函数是运行在DISPATCH_LEVEL的,而内核中的绝大多数函数的运行时irql都不能处在这个中断级别,如ZwCreateFie,ddk文档规定了,这个内核函数必须运行在PASSIVE_LEVEL,如果我们需要在某个DPC函数中调用ZwCreateFie,怎么办呢?一个办法便是将这个工作打包成一条工作项委派给‘系统工作者线程’去执行。
内核中有一个守护线程(其实分成9个线程),运行在PASSIVE_LEVEL,专门用来提供服务,执行别的线程委派给它的工作,这个守护线程就是‘系统工作者线程’。
按照工作项的紧迫程度,分成三种。系统中相应的有三种工作项队列
typedef enum _WORK_QUEUE_TYPE {
CriticalWorkQueue,//较紧迫
DelayedWorkQueue,//最常见
HyperCriticalWorkQueue,//最紧迫
} WORK_QUEUE_TYPE;
CriticalWorkQueue工作项队列上配有5个服务线程,DelayedWorkQueue队列上配有3个服务线程,HyperCriticalWorkQueue上配有1个服务线程。
下面的宏用来构造一条工作项
#define ExInitializeWorkItem(Item,Routine,Context) \
{ \
Item->WorkRoutine=Routine;\
Item->Parameter=Context;\
Item->List.Flink=NULL;\
}
构造好一条工作项后,就可以把这条工作项挂入指定紧迫程度的系统工作项队列中。
VOID
ExQueueWorkItem(IN PWORK_QUEUE_ITEM WorkItem,
IN WORK_QUEUE_TYPE QueueType)//工作项紧迫程度类型
{
PEX_WORK_QUEUE WorkQueue = &ExWorkerQueue[QueueType];//相应的工作项队列
if ((ULONG_PTR)WorkItem->WorkerRoutine < MmUserProbeAddress)//必须位于内核空间
{
KeBugCheckEx(WORKER_INVALID,1, (ULONG_PTR)WorkItem,
(ULONG_PTR)WorkItem->WorkerRoutine,0);
}
KeInsertQueue(&WorkQueue->WorkerQueue, &WorkItem->List);//关键。
if ((WorkQueue->Info.MakeThreadsAsNecessary) &&
(!IsListEmpty(&WorkQueue->WorkerQueue.EntryListHead)) &&
(WorkQueue->WorkerQueue.CurrentCount < WorkQueue->WorkerQueue.MaximumCount) &&
(WorkQueue->DynamicThreadCount < 16))
{
KeSetEvent(&ExpThreadSetManagerEvent, 0, FALSE);
}
}
当把工作项插入到系统对应的工作项队列后,系统中的某个服务线程便会在某一时刻处理该工作项。
9个服务线程的函数都是同一个函数,只是参数不同。我们看:
VOID ExpWorkerThreadEntryPoint(IN PVOID Context)//context参数主要表示工作项队列类型
{
PLARGE_INTEGER TimeoutPointer = NULL;
PETHREAD Thread = PsGetCurrentThread();
if ((ULONG_PTR)Context & EX_DYNAMIC_WORK_THREAD)
{
Timeout.QuadPart = Int32x32To64(10, -10000000 * 60);//1分钟
TimeoutPointer = &Timeout;
}
WorkQueueType = (WORK_QUEUE_TYPE)((ULONG_PTR)Context &~ EX_DYNAMIC_WORK_THREAD);
WorkQueue = &ExWorkerQueue[WorkQueueType];
WaitMode = (UCHAR)WorkQueue->Info.WaitMode;
ASSERT(Thread->ExWorkerCanWaitUser == FALSE);
if (WaitMode == UserMode) Thread->ExWorkerCanWaitUser = TRUE;
if (!ExpWorkersCanSwap) KeSetKernelStackSwapEnable(FALSE);
do
{
if (WorkQueue->Info.QueueDisabled)
{
KeSetKernelStackSwapEnable(TRUE);
PsTerminateSystemThread(STATUS_SYSTEM_SHUTDOWN);//立即终止
}
OldValue = WorkQueue->Info;
NewValue = OldValue;
NewValue.WorkerCount++;//递增该队列上的工作线程计数
}
while (InterlockedCompareExchange((PLONG)&WorkQueue->Info,*(PLONG)&NewValue,
*(PLONG)&OldValue) != *(PLONG)&OldValue);
Thread->ActiveExWorker = TRUE;//标记正式成为一个工作者线程了
ProcessLoop:
for (;;)
{
//等待本服务线程的工作项队列中出现工作项,然后取下来
QueueEntry = KeRemoveQueue(&WorkQueue->WorkerQueue,WaitMode,TimeoutPointer);
if ((NTSTATUS)(ULONG_PTR)QueueEntry == STATUS_TIMEOUT)
break;//等待超时就退出for循环
InterlockedIncrement((PLONG)&WorkQueue->WorkItemsProcessed);//递增已处理计数
WorkItem = CONTAINING_RECORD(QueueEntry, WORK_QUEUE_ITEM, List);
WorkItem->WorkerRoutine(WorkItem->Parameter);//关键。调用执行工作项
if (Thread->Tcb.SpecialApcDisable) Thread->Tcb.SpecialApcDisable = FALSE;
//我们的工作项函数运行在PASSIVE_LEVEL,内部不要修改irql,否则蓝屏
if (KeGetCurrentIrql() != PASSIVE_LEVEL)
{
KeBugCheckEx(WORKER_THREAD_RETURNED_AT_BAD_IRQL,
(ULONG_PTR)WorkItem->WorkerRoutine,KeGetCurrentIrql(),
(ULONG_PTR)WorkItem->Parameter, (ULONG_PTR)WorkItem);
}
if (Thread->ActiveImpersonationInfo)//工作项函数内部不能冒用令牌
{
KeBugCheckEx(IMPERSONATING_WORKER_THREAD, (ULONG_PTR)WorkItem->WorkerRoutine,
(ULONG_PTR)WorkItem->Parameter, (ULONG_PTR)WorkItem,0);
}
}
if (!IsListEmpty(&Thread->IrpList)) goto ProcessLoop; //继续服务
if (WorkQueue->Info.QueueDisabled) goto ProcessLoop; //继续服务
//下面退出服务线程
do
{
OldValue = WorkQueue->Info;
NewValue = OldValue;
NewValue.WorkerCount--;//递减该队列上的服务线程计数
}
while (InterlockedCompareExchange((PLONG)&WorkQueue->Info,*(PLONG)&NewValue,
*(PLONG)&OldValue) != *(PLONG)&OldValue);
InterlockedDecrement(&WorkQueue->DynamicThreadCount);
Thread->ActiveExWorker = FALSE;
KeSetKernelStackSwapEnable(TRUE);
return;
}
上面这段代码想必不用过多解释了
[9]Windows内核情景分析 --- DPC的更多相关文章
- windows内核情景分析之—— KeRaiseIrql函数与KeLowerIrql()函数
windows内核情景分析之—— KeRaiseIrql函数与KeLowerIrql()函数 1.KeRaiseIrql函数 这个 KeRaiseIrql() 只是简单地调用 hal 模块的 KfRa ...
- 几个常用内核函数(《Windows内核情景分析》)
参考:<Windows内核情景分析> 0x01 ObReferenceObjectByHandle 这个函数从句柄得到对应的内核对象,并递增其引用计数. NTSTATUS ObRefer ...
- [1]windows 内核情景分析---说明
本文说明:这一系列文章(笔记)是在看雪里面下载word文档,现转帖出来,希望更多的人能看到并分享,感谢原作者的分享精神. 说明 本文结合<Windows内核情景分析>(毛德操著).< ...
- [7] Windows内核情景分析---线程同步
基于同步对象的等待.唤醒机制: 一个线程可以等待一个对象或多个对象而进入等待状态(也叫睡眠状态),另一个线程可以触发那个等待对象,唤醒在那个对象上等待的所有线程. 一个线程可以等待一个对象或多个对象, ...
- [14]Windows内核情景分析 --- 文件系统
文件系统 一台机器上可以安装很多物理介质来存放资料(如磁盘.光盘.软盘.U盘等).各种物理介质千差万别,都配备有各自的驱动程序,为了统一地访问这些物理介质,windows设计了文件系统机制.应用程序要 ...
- [11]Windows内核情景分析---设备驱动
设备驱动 设备栈:从上层到下层的顺序依次是:过滤设备.类设备.过滤设备.小端口设备[过.类.过滤.小端口] 驱动栈:因设备堆栈原因而建立起来的一种堆栈 老式驱动:指不提供AddDevice的驱动,又叫 ...
- [4]Windows内核情景分析---内核对象
写过Windows应用程序的朋友都常常听说"内核对象"."句柄"等术语却无从得知他们的内核实现到底是怎样的, 本篇文章就揭开这些技术的神秘面纱. 常见的内核对象 ...
- [2]windows内核情景分析--系统调用
Windows的地址空间分用户模式与内核模式,低2GB的部分叫用户模式,高2G的部分叫内核模式,位于用户空间的代码不能访问内核空间,位于内核空间的代码却可以访问用户空间 一个线程的运行状态分内核态与用 ...
- [15]Windows内核情景分析 --- 权限管理
Windows系统是支持多用户的.每个文件可以设置一个访问控制表(即ACL),在ACL中规定每个用户.每个组对该文件的访问权限.不过,只有Ntfs文件系统中的文件才支持ACL. (Ntfs文件系统中, ...
随机推荐
- 常用css属性
一.常用css属性 (1) *block(区块) 行高 line-height:数值 | inherit | normal;字间距 letter-spacing: 数值 | inherit | nor ...
- flask框架詳解
https://www.cnblogs.com/sss4/p/8097653.html 前言: Django:1个重武器,包含了web开发中常用的功能.组件的框架:(ORM.Session.Form. ...
- js转换Date日期格式
有时候做项目会用到js的date日期格式,因为Date()返回的格式不是我们需要的, Date()返回格式: Thu Mar 19 2015 12:00:00 GMT+0800 (中国标准时间) 而我 ...
- 函数datetime
datetime import datetime.datetime print(datetime.now()) #2019-03-20 11:35:25.(471359)毫秒 # 时间对象 f = d ...
- Why does the memory usage increase when I redeploy a web application?
That is because your web application has a memory leak. A common issue are "PermGen" memor ...
- Laravel展示产品-CRUD之show
上一篇讲了Laravel创建产品-CRUD之Create and Store,现在我们来做产品展示模块,用到是show,①首先我们先修改controller,文件是在/app/Http/Control ...
- 电脑出现 flash update failed 解决方法
笔记本电脑过了一个周末打开时出现以上问题,每次都进入这个界面 解决方法: 拆机,插拨一下内存条,硬盘,就启动了
- HIVE简单操作
1.hive命令登录HIVE数据库后,执行show databases;命令可以看到hive数据库中有一个默认的default数据库. [root@hadoop hive]# hive Logging ...
- Nginx的基础配置管理
Nginx的基本功能 1.静态资源的web服务器 2.http协议反向代理服务器 3.tcp/udp协议的请求转发 安装nginx yum install epel-release yum insta ...
- NYOJ 方案数量
1.递归求解(直接递归会超时,要用备忘录法) # include<iostream> # include<stdio.h> #include <map> using ...