ReactOS分析windows APC机制
#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提交。 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提交。 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |
