[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文件系统中, ...
随机推荐
- azkaban---visualize crontab--frontail
azkaban---visualize crontab azkaban--docker-----http://www.jkeabc.com/254015.html azkaban--tips ht ...
- iOS 限制输入字数
关于限制输入字数以前也做过,网上也很多方法.但都不够完美,本方法可防止中文联想.粘贴等突破长途限制.可防止Emoji截为两半导致编码出问题. - (void)textFieldDidChange:(U ...
- JRE vs OpenJDK vs Oracle JDK
JRE vs OpenJDK vs Oracle JDK 在我们继续了解如何安装Java之前,让我们快速地了解JRE.OpenJDK和Oracle JDK之间的不同之处. JRE(Java Runti ...
- 《linux 计划任务》- cron
一:什么是计划任务 - 你给手机定了一个闹钟,每天的 7:00 会准时响铃叫你起床,这实际上就是一个计划任务 - 所谓定时任务,就是在已经定好的特定时间去执行的事情. - Cron是一个[守护程序]用 ...
- 20165336 实验三 敏捷开发与XP实践
20165336 实验三 敏捷开发与XP实践 一.实验报告封面 课程:Java程序设计 班级:1653班 姓名:康志强 学号:20165336 指导教师:娄嘉鹏 实验日期:2018年4月28日 实验时 ...
- 【PyQt5-Qt Designer】QLineEdit 文本输入
QLineEdit 文本输入 一.QlineEdit 基本方法 setAlignment() 按固定值方式对齐文本 Qt.AlignLeft:水平方向靠左对齐 Qt.AlignRight:水平方向靠右 ...
- 【python基础】迭代器和生成器函数
1.迭代器协议: 1.迭代器协议是指:对象必须提供一个 __next__() 方法,执行该方法要么返回迭代中的下一项,要么就引起一个StopIteration异常,以终止迭代(只能往后走不能往前退) ...
- 关于安装SVN Service 出错 Service 'VisualSVN Server' failed to start. Please check VisualSVN Server log in Event Viewer for more details
关于安装SVN Service 出错 Service 'VisualSVN Server' failed to start. Please check VisualSVN Server log in ...
- 【Linux】阿里云服务器部署--禅道
Xshell部署环境 回到Xshell界面,连上阿里云服务器,参考上一篇[linux学习1-Xshell连接阿里云ECS服务器](https://www.cnblogs.com/yoyoketang/ ...
- VS Code 管理 .NET Core解决方案
本练习要使用Visual studio code完成一个包含多个项目的解决方案,包括类库和Web项目.结合Visual Studio Code和.NET Core CLI,创建项目结构如下: pied ...