#define KeEnterGuardedRegion() \ { \ PKTHREAD _Thread = KeGetCurrentThread(); \ ASSERT(KeGetCurrentIrql() <= APC_LEVEL); \ ASSERT(_Thread == KeGetCurrentThread()); \ ASSERT((_Thread->SpecialApcDisable <= 0) && \ (_Thread->SpecialApcDisable != -32768)); \ _Thread->SpecialApcDisable--; \ }
#define KeEnterCriticalRegion() \ { \ PKTHREAD _Thread = KeGetCurrentThread(); \ ASSERT(_Thread == KeGetCurrentThread()); \ ASSERT((_Thread->KernelApcDisable <= 0) && \ (_Thread->KernelApcDisable != -32768)); \ _Thread->KernelApcDisable--; \ }
根据上面两个宏的定义可以看出,GuardedRegion和CriticalRegion的区别就是,前者禁止特殊内核模式下的APC,而后者禁止除特殊内核模式的APC。KeEnterCriticalRegion临时禁止普通内核模式APC的执行,但是不禁止特殊内核模式的APC的执行。而前者则会临时禁止所有的APC的提交。由于,这两个的标记是通过特定的线程的计数实现的,所以这里仅仅只做为本线程内的同步。而APC会在线程调度运行之前被提交,这里通过一定线程标记计数就实现阻止APC的提交。注意:这里的criticalregion和guardedregion与critical section无关。
DWORD WINAPI QueueUserAPC(PAPCFUNC pfnAPC,HANDLE hThread,ULONG_PTR dwData) { NTSTATUS Status; Status = NtQueueApcThread(hThread,IntCallUserApc,pfnAPC,(PVOID)dwData,NULL); if (!NT_SUCCESS(Status)) { BaseSetLastNTError(Status); return 0; } return 1; } <pre name="code" class="cpp">static void CALLBACK IntCallUserApc(PVOID Function,PVOID dwData,PVOID Argument3) { PAPCFUNC pfnAPC = (PAPCFUNC)Function; pfnAPC((ULONG_PTR)dwData); }如果需要在用户空间使用APC,可以调用QueueUserApc函数。这个函数有三个参数,第一个参数是提交APC的时候被调用的函数,第二个参数是与APC相关联的线程的句柄,第三个参数是执行第一个函数时传递进去的参数。至于IntCallUserApc,属于一个stub。真正的APC处理是在IntCallUserApc中进行。这个函数属性是static,表明这个函数只能在这个文件中使用。这样避免以后的APC需要更多的参数的时候,可以在上层接口不变的情况下进行改变。
NTSTATUS NTAPI NtQueueApcThread(IN HANDLE ThreadHandle,IN PKNORMAL_ROUTINE ApcRoutine,IN PVOID NormalContext,IN PVOID SystemArgument1,IN PVOID SystemArgument2) { PKAPC Apc; PETHREAD Thread; NTSTATUS Status = STATUS_SUCCESS; Status = ObReferenceObjectByHandle(ThreadHandle,THREAD_SET_CONTEXT,PsThreadType,ExGetPrevIoUsMode(),(PVOID)&Thread,NULL); if (!NT_SUCCESS(Status)) return Status; if (Thread->SystemThread) { Status = STATUS_INVALID_HANDLE; goto Quit; } Apc = ExAllocatePoolWithTag(NonPagedPool | POOL_QUOTA_FAIL_INSTEAD_OF_RAISE,sizeof(KAPC),TAG_PS_APC); if (!Apc) { Status = STATUS_NO_MEMORY; goto Quit; } KeInitializeApc(Apc,&Thread->Tcb,OriginalApcEnvironment,PspQueueApcSpecialApc,NULL,ApcRoutine,UserMode,NormalContext); if (!KeInsertQueueApc(Apc,SystemArgument1,SystemArgument2,IO_NO_INCREMENT)) { ExFreePool(Apc); Status = STATUS_UNSUCCESSFUL; } Quit: ObDereferenceObject(Thread); return Status; } VOID NTAPI PspQueueApcSpecialApc(IN PKAPC Apc,IN OUT PKNORMAL_ROUTINE* NormalRoutine,IN OUT PVOID* NormalContext,IN OUT PVOID* SystemArgument1,IN OUT PVOID* SystemArgument2) { ExFreePool(Apc); }首先会经过一些验证,尤其需要注意的是这里的APC模式需要是用户模式,否则程序会崩溃。因为最初调用的是用户空间的函数,所以这里必须是用户模式。其实并非内核模式下不能使用APC,而是APC需要和特定的线程对应,而在大部分情况下的内核模式的线程是任意的。在验证之后,利用对象管理组件提供的函数实现将句柄转化为对象。然后调用内存管理分配一个APC对象。初始化和挂载到队列是关键操作,下面具体分析。
VOID NTAPI KeInitializeApc(IN PKAPC Apc,IN PKTHREAD Thread,IN KAPC_ENVIRONMENT TargetEnvironment,IN PKKERNEL_ROUTINE KernelRoutine,IN PKRUNDOWN_ROUTINE RundownRoutine OPTIONAL,IN PKNORMAL_ROUTINE NormalRoutine,IN KPROCESSOR_MODE Mode,IN PVOID Context) { Apc->Type = ApcObject; Apc->Size = sizeof(KAPC); if (TargetEnvironment == CurrentApcEnvironment) { Apc->ApcStateIndex = Thread->ApcStateIndex; } else { Apc->ApcStateIndex = TargetEnvironment; } Apc->Thread = Thread; Apc->KernelRoutine = KernelRoutine; Apc->RundownRoutine = RundownRoutine; Apc->NormalRoutine = NormalRoutine; if (NormalRoutine) { Apc->ApcMode = Mode; Apc->NormalContext = Context; } else { Apc->ApcMode = KernelMode; Apc->NormalContext = NULL; } Apc->Inserted = FALSE; }根据上面传递进来的参数,可以发现其中NormalRoutine被设置为IntCallUserApc,而原始的APC函数作为NormalContext存在。而KernelRoutine类似于C++语言中的析构函数,在APC调用之后进行析构操作。传递进来的TargetEnviroment参数,决定APC使用哪个状态。ApcStateIndex成员变量会在以后用到。而如果NormalRoutine为空的时候,APC一定是内核模式的APC,同样的NormalContext无效,所以设置为NULL。最后Inserted成员变量设置为FALSE,表明这个APC还没有被挂载到线程的APC队列。KeInsertQueueApc函数只是简单地提供互斥访问,然后将控制权传递给内部的KiInsertQueueApc函数。
VOID FASTCALL KiInsertQueueApc(IN PKAPC Apc,IN KPRIORITY PriorityBoost) { PKTHREAD Thread = Apc->Thread; PKAPC_STATE ApcState; KPROCESSOR_MODE ApcMode; PLIST_ENTRY ListHead,NextEntry; PKAPC QueuedApc; PKGATE Gate; NTSTATUS Status; BOOLEAN RequestInterrupt = FALSE; if (Apc->ApcStateIndex == InsertApcEnvironment)//查看是否调用者希望使用线程的环境 { Apc->ApcStateIndex = Thread->ApcStateIndex; }//两种APC环境,要么使用原始环境,要么使用线程的环境 ApcState = Thread->ApcStatePointer[(UCHAR)Apc->ApcStateIndex]; ApcMode = Apc->ApcMode; if (Apc->NormalRoutine)//APC最后执行的函数 { if ((ApcMode != KernelMode) && (Apc->KernelRoutine == PsExitSpecialApc))//其中PsExitSpecialApc释放APC,并退出线程 { Thread->ApcState.UserApcPending = TRUE; InsertHeadList(&ApcState->ApcListHead[ApcMode],&Apc->ApcListEntry); } else { InsertTailList(&ApcState->ApcListHead[ApcMode],&Apc->ApcListEntry); } } else//如果不存在Normal例程,则找到同样不存在例程的APC,放到这个APC的后面 { ListHead = &ApcState->ApcListHead[ApcMode]; NextEntry = ListHead->Blink; while (NextEntry != ListHead) { QueuedApc = CONTAINING_RECORD(NextEntry,KAPC,ApcListEntry); if (!QueuedApc->NormalRoutine) break; NextEntry = NextEntry->Blink; } InsertHeadList(NextEntry,&Apc->ApcListEntry); } if (Thread->ApcStateIndex == Apc->ApcStateIndex)//如果APC和线程公用环境。并且当前环境一样 { if (Thread == KeGetCurrentThread())// { if (ApcMode == KernelMode)//当前APC模式是内核模式 { Thread->ApcState.KernelApcPending = TRUE;//设置当前线程为内核APC悬挂 if (!Thread->SpecialApcDisable) { HalRequestSoftwareInterrupt(APC_LEVEL);//调用提交APC } } } else//在当前线程插入其他线程的APC队列时,会引起调度 { KiAcquireDispatcherLock(); if (ApcMode == KernelMode) { Thread->ApcState.KernelApcPending = TRUE; if (Thread->State == Running)//如果需要提交的目标线程正在运行 { RequestInterrupt = TRUE; } else if ((Thread->State == Waiting) && (Thread->WaitIrql == PASSIVE_LEVEL) && !(Thread->SpecialApcDisable) && (!(Apc->NormalRoutine) || (!(Thread->KernelApcDisable) && !(Thread->ApcState.KernelApcInProgress)))) { Status = STATUS_KERNEL_APC; KiUnwaitThread(Thread,Status,PriorityBoost);//唤醒被提交的线程,将这个线程添加到就绪队列当中 } else if (Thread->State == GateWait) { KiAcquireThreadLock(Thread); if ((Thread->State == GateWait) && (Thread->WaitIrql == PASSIVE_LEVEL) && !(Thread->SpecialApcDisable) && (!(Apc->NormalRoutine) || (!(Thread->KernelApcDisable) && !(Thread->ApcState.KernelApcInProgress)))) { Gate = Thread->GateObject; KiAcquireDispatcherObject(&Gate->Header); RemoveEntryList(&Thread->WaitBlock[0].WaitListEntry); KiReleaseDispatcherObject(&Gate->Header); if (Thread->Queue) Thread->Queue->CurrentCount++; Thread->WaitStatus = STATUS_KERNEL_APC; KiInsertDeferredReadyList(Thread); } KiReleaseThreadLock(Thread); } } else if ((Thread->State == Waiting) && (Thread->WaitMode == UserMode) && ((Thread->Alertable) || (Thread->ApcState.UserApcPending)))//两种状态下的APC唤醒 { Thread->ApcState.UserApcPending = TRUE; Status = STATUS_USER_APC; KiUnwaitThread(Thread,PriorityBoost); } KiReleaseDispatcherLockFromDpcLevel(); KiRequestApcInterrupt(RequestInterrupt,Thread->NextProcessor); } } }整个函数分为三个部分,第一部分测试应该使用哪个ApcState,第二部分根据APC的NormalRoutine存在与否以及APC的模式来判断应该讲APC存放在哪个队列中。ApcState实际上只有连个索引,一个用于线程的执行状态,而另一个用于APC的执行状态。所以在第三部分当中,首先利用一个if语句判断是否APC等于线程的ApcState索引——此时刚好在线程的执行中,可以根据线程的APC状态判断是否进行APC提交。如果挂载到线程的APC是内核模式的APC,同时被提交的线程是当前线程,则可以立即进行APC提交。
如果,要提交的APC不属于当前线程,则首先获取全局的内核调度锁,防止线程被调度。然后,判断需要被提交的APC是内核模式还是用户模式。在内核模式分成三种情况,第一,提交的线程正在运行,则申请中断线程的运行。第二,当前线程正处于等待状态,则判断条件相对复杂一些。在等待的时候,需要等待的IRQL是PASSIVE_LEVEL,同时特殊内核模式的APC不能被禁止(上面的Guarded Region),内核模式的APC也不能被禁止(上面的Critical Region),最后一个是表明当前的APC并不是处于提交过程中,那么就唤醒需要被提交APC的线程。
接下来的gate waiting和上面的waiting很类似。只不过waiting状态下可能引起线程优先级的提升。
最后一种状态是,用户模式下的APC。只有在被提交APC的线程是等待状态,并且警醒状态为真的时候,才会唤醒需要提交APC的线程。
最后调用KiRequestApcInterrupt申请APC提交。
最后,稍微分析一下APC的提交过程。APC的提交在这里主要是调用HalRequestSoftwareInterrupt函数,主要处理过程在之前的中断调用中有所介绍。最终这个函数的调用会落到KeDeliverApc。
VOID NTAPI KiCheckForKernelApcDelivery(VOID) { KIRQL OldIrql; if (KeGetCurrentIrql() == PASSIVE_LEVEL) { KeRaiseIrql(APC_LEVEL,&OldIrql); KiDeliverApc(KernelMode,0); KeLowerIrql(PASSIVE_LEVEL); } else { KeGetCurrentThread()->ApcState.KernelApcPending = TRUE; HalRequestSoftwareInterrupt(APC_LEVEL); } }通过上面的函数不难发现,APC的提交需要在最低的PASSIVE_LEVEL级别。如果不在PASSIVE_LEVEL级别的话,会调用HalReuquestSoftWareInterrupt函数将当前的请求保存起来,知道降低到PASSIVE_LEVEL级别的时候重新进行APC提交。
VOID NTAPI KiDeliverApc(IN KPROCESSOR_MODE DeliveryMode,IN PKEXCEPTION_FRAME ExceptionFrame,IN PKTRAP_FRAME TrapFrame) { PKTHREAD Thread = KeGetCurrentThread(); PKPROCESS Process = Thread->ApcState.Process; PKTRAP_FRAME OldTrapFrame; PLIST_ENTRY ApcListEntry; PKAPC Apc; KLOCK_QUEUE_HANDLE ApcLock; PKKERNEL_ROUTINE KernelRoutine; PVOID NormalContext; PKNORMAL_ROUTINE NormalRoutine; PVOID SystemArgument1; PVOID SystemArgument2; ASSERT_IRQL_EQUAL(APC_LEVEL); OldTrapFrame = Thread->TrapFrame; Thread->TrapFrame = TrapFrame; Thread->ApcState.KernelApcPending = FALSE; if (Thread->SpecialApcDisable) goto Quickie; while (!IsListEmpty(&Thread->ApcState.ApcListHead[KernelMode])) { KiAcquireApcLockAtApcLevel(Thread,&ApcLock); if (IsListEmpty(&Thread->ApcState.ApcListHead[KernelMode])) { KiReleaseApcLock(&ApcLock); break; } Thread->ApcState.KernelApcPending = FALSE; ApcListEntry = Thread->ApcState.ApcListHead[KernelMode].Flink; Apc = CONTAINING_RECORD(ApcListEntry,ApcListEntry); NormalRoutine = Apc->NormalRoutine; KernelRoutine = Apc->KernelRoutine; NormalContext = Apc->NormalContext; SystemArgument1 = Apc->SystemArgument1; SystemArgument2 = Apc->SystemArgument2; if (!NormalRoutine) { RemoveEntryList(ApcListEntry); Apc->Inserted = FALSE; KiReleaseApcLock(&ApcLock); KernelRoutine(Apc,&NormalRoutine,&NormalContext,&SystemArgument1,&SystemArgument2); if (KeGetCurrentIrql() != ApcLock.OldIrql) { KeBugCheckEx(IRQL_UNEXPECTED_VALUE,(KeGetCurrentIrql() << 16) | (ApcLock.OldIrql << 8),(ULONG_PTR)KernelRoutine,(ULONG_PTR)Apc,(ULONG_PTR)NormalRoutine); } } else { if ((Thread->ApcState.KernelApcInProgress) || (Thread->KernelApcDisable)) { KiReleaseApcLock(&ApcLock); goto Quickie; } RemoveEntryList(ApcListEntry); Apc->Inserted = FALSE; KiReleaseApcLock(&ApcLock); KernelRoutine(Apc,(ULONG_PTR)NormalRoutine); } if (NormalRoutine) { Thread->ApcState.KernelApcInProgress = TRUE; KeLowerIrql(PASSIVE_LEVEL); NormalRoutine(NormalContext,SystemArgument2); KeRaiseIrql(APC_LEVEL,&ApcLock.OldIrql); } Thread->ApcState.KernelApcInProgress = FALSE; } } if ((DeliveryMode == UserMode) && !(IsListEmpty(&Thread->ApcState.ApcListHead[UserMode])) && (Thread->ApcState.UserApcPending)) { KiAcquireApcLockAtApcLevel(Thread,&ApcLock); Thread->ApcState.UserApcPending = FALSE; if (IsListEmpty(&Thread->ApcState.ApcListHead[UserMode])) { KiReleaseApcLock(&ApcLock); goto Quickie; } ApcListEntry = Thread->ApcState.ApcListHead[UserMode].Flink; Apc = CONTAINING_RECORD(ApcListEntry,ApcListEntry); NormalRoutine = Apc->NormalRoutine; KernelRoutine = Apc->KernelRoutine; NormalContext = Apc->NormalContext; SystemArgument1 = Apc->SystemArgument1; SystemArgument2 = Apc->SystemArgument2; RemoveEntryList(ApcListEntry); Apc->Inserted = FALSE; KiReleaseApcLock(&ApcLock); KernelRoutine(Apc,&SystemArgument2); if (!NormalRoutine) { KeTestAlertThread(UserMode); } else { KiInitializeUserApc(ExceptionFrame,TrapFrame,NormalRoutine,NormalContext,SystemArgument2); } } Quickie: if (Process != Thread->ApcState.Process) { KeBugCheckEx(INVALID_PROCESS_ATTACH_ATTEMPT,(ULONG_PTR)Process,(ULONG_PTR)Thread->ApcState.Process,Thread->ApcStateIndex,KeGetCurrentPrcb()->DpcRoutineActive); } Thread->TrapFrame = OldTrapFrame; }主要的操作是对线程的APC队列进行加锁,并取下队列节点。然后调用之前设置好的函数将APC内存给释放掉,同时调用NormalRoutine函数进行APC提交。