加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 百科 > 正文

ReactOS分析windows APC机制

发布时间:2020-12-15 04:54:28 所属栏目:百科 来源:网络整理
导读:#define KeEnterGuardedRegion() { PKTHREAD _Thread = KeGetCurrentThread(); ASSERT(KeGetCurrentIrql() = APC_LEVEL); ASSERT(_Thread == KeGetCurrentThread()); ASSERT((_Thread-SpecialApcDisable = 0) (_Thread-SpecialApcDisable != -327
#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提交。

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读