APC异步过程调用
+ -

APC挂入

2023-08-02 22 0

挂入ApcListHead链表中的叫做APC,每个APC的结构如下:

2: kd> dt _KAPC
nt!_KAPC
   +0x000 Type             : UChar
   +0x001 SpareByte0       : UChar
   +0x002 Size             : UChar
   +0x003 SpareByte1       : UChar
   +0x004 SpareLong0       : Uint4B
   +0x008 Thread           : Ptr64 _KTHREAD
   +0x010 ApcListEntry     : _LIST_ENTRY
   +0x020 KernelRoutine    : Ptr64     void 
   +0x028 RundownRoutine   : Ptr64     void 
   +0x030 NormalRoutine    : Ptr64     void 
   +0x020 Reserved         : [3] Ptr64 Void
   +0x038 NormalContext    : Ptr64 Void
   +0x040 SystemArgument1  : Ptr64 Void
   +0x048 SystemArgument2  : Ptr64 Void
   +0x050 ApcStateIndex    : Char
   +0x051 ApcMode          : Char
   +0x052 Inserted         : UChar

其中:

  • Type:内核对象APC编号
  • Size:结构体大小
  • SpareByte0/SpareByte1/SpareLong0:用于字节对齐的保留字段。
  • Thread:APC所属线程指针,即目标线程
  • ApcListEntry:链表
  • KernelRoutine:指向内核APC调用完成后释放APC的函数指针。为ExFreePoolWithTag
  • RundownRoutine:未知
  • NormalRoutine:内核APC时,为APC函数的入口地址。应用APC时,为应用层APC的总入口地址
  • NormalContext:内核APC时,为NULL,应用层APC时,为APC的入口地址。
  • SystemArgument1/SystemArgument2:APC参数
  • ApcStateIndex:挂入APC的队列标识,值为0~3
  • ApcMode:内核模式还是应用层模式APC
  • Inserted:APC是否已经挂入线程APC队列。

APC的挂入

应用层挂入的APC流程如下:

  • QueueUserAPC(kernel32.dll)
    • NtQueueApcThread(ntoskrnl)
      • KeInitializeApc(分配空间,初始化APC结构)
      • KeInertQueueApc
        • KiInsertQueueApc

在内核 层也是通过KeInitializeApc和KeInertQueueApc实现的。
这里以ReactOS源代码说明:

VOID NTAPI KeInitializeApc    (IN PKAPC     Apc,  //APC结构体
IN PKTHREAD     Thread,                        //目标线程
IN KAPC_ENVIRONMENT     TargetEnvironment, //目标环境0~3
IN PKKERNEL_ROUTINE     KernelRoutine,        //销毁APC的函数
IN PKRUNDOWN_ROUTINE RundownRoutine     OPTIONAL,//可选
IN PKNORMAL_ROUTINE     NormalRoutine,        //内核APC函数或用户APC总入口
IN KPROCESSOR_MODE     Mode,                    //用户层APC还是内核层APC,UserMode,KernelMode
IN PVOID     Context                         //用户层APC或内核层NULL
)    
{
    /* Sanity check */
    ASSERT(TargetEnvironment <= InsertApcEnvironment);

    /* Set up the basic APC Structure Data */
    Apc->Type = ApcObject;
    Apc->Size = sizeof(KAPC);

    /* Set the Environment */
    if (TargetEnvironment == CurrentApcEnvironment)
    {
        /* Use the current one for the thread */
        Apc->ApcStateIndex = Thread->ApcStateIndex;
    }
    else
    {
        /* Sanity check */
        ASSERT((TargetEnvironment <= Thread->ApcStateIndex) ||
               (TargetEnvironment == InsertApcEnvironment));

        /* Use the one that was given */
        Apc->ApcStateIndex = TargetEnvironment;
    }

    /* Set the Thread and Routines */
    Apc->Thread = Thread;
    Apc->KernelRoutine = KernelRoutine;
    Apc->RundownRoutine = RundownRoutine;
    Apc->NormalRoutine = NormalRoutine;

    /* Check if this is a special APC */
    if (NormalRoutine)//普通的APC
    {
        /* It's a normal one. Set the context and mode */
        Apc->ApcMode = Mode;
        Apc->NormalContext = Context;
    }
    else
    {
        /* It's a special APC, which can only be kernel mode */
        Apc->ApcMode = KernelMode;
        Apc->NormalContext = NULL;
    }

    /* The APC is not inserted */
    Apc->Inserted = FALSE;
}

其中的:

typedef enum _KAPC_ENVIRONMENT
{
    OriginalApcEnvironment,  //原始环境
    AttachedApcEnvironment,  //挂靠环境
    CurrentApcEnvironment,    //当前线程环境
    InsertApcEnvironment    //插入APC时的环境
} KAPC_ENVIRONMENT;

插入APC:

BOOLEAN NTAPI KeInsertQueueApc    (    IN PKAPC     Apc,
IN PVOID     SystemArgument1,
IN PVOID     SystemArgument2,
IN KPRIORITY     PriorityBoost 
)    
{
    PKTHREAD Thread = Apc->Thread;
    KLOCK_QUEUE_HANDLE ApcLock;
    BOOLEAN State = TRUE;
    ASSERT_APC(Apc);
    ASSERT_IRQL_LESS_OR_EQUAL(DISPATCH_LEVEL);

    /* Get the APC lock */
    KiAcquireApcLockRaiseToSynch(Thread, &ApcLock);

    /* Make sure we can Queue APCs and that this one isn't already inserted */
    if (!(Thread->ApcQueueable) || (Apc->Inserted))
    {
        /* Fail */
        State = FALSE;
    }
    else
    {
        /* Set the System Arguments and set it as inserted */
        Apc->SystemArgument1 = SystemArgument1;
        Apc->SystemArgument2 = SystemArgument2;
        Apc->Inserted = TRUE;

        /* Call the Internal Function */
        KiInsertQueueApc(Apc, PriorityBoost);
    }

    /* Release the APC lock and return success */
    KiReleaseApcLockFromSynchLevel(&ApcLock);
    KiExitDispatcher(ApcLock.OldIrql);
    return State;
}

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;

    /*
     * Check if the caller wanted this APC to use the thread's environment at
     * insertion time.
     */
     //插入时的APC环境
    if (Apc->ApcStateIndex == InsertApcEnvironment)
    {
        /* Copy it over */
        Apc->ApcStateIndex = Thread->ApcStateIndex;
    }

    /* Get the APC State for this Index, and the mode too */
    ApcState = Thread->ApcStatePointer[(UCHAR)Apc->ApcStateIndex];
    ApcMode = Apc->ApcMode;

    /* The APC must be "inserted" already */
    ASSERT(Apc->Inserted == TRUE);

    /* Three scenarios:
     * 1) Kernel APC with Normal Routine or User APC = Put it at the end of the List
     * 2) User APC which is PsExitSpecialApc = Put it at the front of the List
     * 3) Kernel APC without Normal Routine = Put it at the end of the No-Normal Routine Kernel APC list
     */
     //含有入口的,就是普通的APC
    if (Apc->NormalRoutine)
    {
        /* Normal APC; is it the Thread Termination APC? */
        if ((ApcMode != KernelMode) &&
            (Apc->KernelRoutine == PsExitSpecialApc))
        {
            /* Set User APC pending to true */
            Thread->ApcState.UserApcPending = TRUE;

            /* Insert it at the top of the list */
            InsertHeadList(&ApcState->ApcListHead[ApcMode],
                           &Apc->ApcListEntry);
        }
        else
        {
            /* Regular user or kernel Normal APC */
            InsertTailList(&ApcState->ApcListHead[ApcMode],
                           &Apc->ApcListEntry);
        }
    }
    else
    {
        /* Special APC, find the last one in the list */
        ListHead = &ApcState->ApcListHead[ApcMode];
        NextEntry = ListHead->Blink;
        while (NextEntry != ListHead)
        {
            /* Get the APC */
            QueuedApc = CONTAINING_RECORD(NextEntry, KAPC, ApcListEntry);

            /* Is this a No-Normal APC? If so, break */
            if (!QueuedApc->NormalRoutine) break;

            /* Move to the previous APC in the Queue */
            NextEntry = NextEntry->Blink;
        }

        /* Insert us here */
        InsertHeadList(NextEntry, &Apc->ApcListEntry);
    }

    /* Now check if the Apc State Indexes match */
    if (Thread->ApcStateIndex == Apc->ApcStateIndex)
    {
        /* Check that the thread matches */
        if (Thread == KeGetCurrentThread())
        {
            /* Sanity check */
            ASSERT(Thread->State == Running);

            /* Check if this is kernel mode */
            if (ApcMode == KernelMode)
            {
                /* All valid, a Kernel APC is pending now */
                Thread->ApcState.KernelApcPending = TRUE;

                /* Check if Special APCs are disabled */
                if (!Thread->SpecialApcDisable)
                {
                    /* They're not, so request the interrupt */
                    HalRequestSoftwareInterrupt(APC_LEVEL);
                }
            }
        }
        else
        {
            /* Acquire the dispatcher lock */
            KiAcquireDispatcherLock();

            /* Check if this is a kernel-mode APC */
            if (ApcMode == KernelMode)
            {
                /* Kernel-mode APC, set us pending */
                Thread->ApcState.KernelApcPending = TRUE;

                /* Are we currently running? */
                if (Thread->State == Running)
                {
                    /* The thread is running, so remember to send a request */
                    RequestInterrupt = TRUE;
                }
                else if ((Thread->State == Waiting) &&
                         (Thread->WaitIrql == PASSIVE_LEVEL) &&
                         !(Thread->SpecialApcDisable) &&
                         (!(Apc->NormalRoutine) ||
                          (!(Thread->KernelApcDisable) &&
                           !(Thread->ApcState.KernelApcInProgress))))
                {
                    /* We'll unwait with this status */
                    Status = STATUS_KERNEL_APC;

                    /* Wake up the thread */
                    KiUnwaitThread(Thread, Status, PriorityBoost);
                }
                else if (Thread->State == GateWait)
                {
                    /* Lock the thread */
                    KiAcquireThreadLock(Thread);

                    /* Essentially do the same check as above */
                    if ((Thread->State == GateWait) &&
                        (Thread->WaitIrql == PASSIVE_LEVEL) &&
                        !(Thread->SpecialApcDisable) &&
                        (!(Apc->NormalRoutine) ||
                         (!(Thread->KernelApcDisable) &&
                          !(Thread->ApcState.KernelApcInProgress))))
                    {
                        /* We were in a gate wait. Handle this. */
                        DPRINT1("A thread was in a gate wait\n");

                        /* Get the gate */
                        Gate = Thread->GateObject;

                        /* Lock the gate */
                        KiAcquireDispatcherObject(&Gate->Header);

                        /* Remove it from the waiters list */
                        RemoveEntryList(&Thread->WaitBlock[0].WaitListEntry);

                        /* Unlock the gate */
                        KiReleaseDispatcherObject(&Gate->Header);

                        /* Increase the queue counter if needed */
                        if (Thread->Queue) Thread->Queue->CurrentCount++;

                        /* Put into deferred ready list with this status */
                        Thread->WaitStatus = STATUS_KERNEL_APC;
                        KiInsertDeferredReadyList(Thread);
                    }

                    /* Release the thread lock */
                    KiReleaseThreadLock(Thread);
                }
            }
            else if ((Thread->State == Waiting) &&
                     (Thread->WaitMode == UserMode) &&
                     ((Thread->Alertable) ||
                      (Thread->ApcState.UserApcPending)))
            {
                /* Set user-mode APC pending */
                Thread->ApcState.UserApcPending = TRUE;
                Status = STATUS_USER_APC;

                /* Wake up the thread */
                KiUnwaitThread(Thread, Status, PriorityBoost);
            }

            /* Release dispatcher lock */
            KiReleaseDispatcherLockFromSynchLevel();

            /* Check if an interrupt was requested */
            KiRequestApcInterrupt(RequestInterrupt, Thread->NextProcessor);
        }
    }
}
`

0 篇笔记 写笔记

IRP完成APC执行函数IopCompleteRequest
IRP在完成时调用IoCompleteRequest,其最终会执行一个APC调用,该调用的函数名为IopCompleteRequest。其调用APC调用时的代码如下:KeInitializeApc(&Irp->Tail.Apc, &......
APC本质
APC全称Asynchronous Procedure Call,中文名异步过程调用。程序是以进程为载体的,其执行体是进程中的线程。一个线程在运行的过程中,因为其占有的CPU,其它进程或者线程是无法占用CPU的,所以从理论上来讲,这个线程是无法被杀死,挂起和恢复的。但在实际的软件开发中,以上的操作......
APC挂入
挂入ApcListHead链表中的叫做APC,每个APC的结构如下:2: kd> dt _KAPCnt!_KAPC +0x000 Type : UChar +0x001 SpareByte0 : UChar +0x002 Size ......
APC的执行及执行时机
当线程中的ApcListHead链表中不为空时,就表示该线程拥有APC,线程应在合适的时机执行该APCAPC执行时机APC常见的执行时机:线程切换:当当前线程处于阻塞状态,比如等待用户输入或者等待磁盘IO完成时,操作系统可以选择在这个时机执行排队中的 APC。这样可以充分利用线程的等待时间,......
SavedApcState与线程挂靠
_KTHREAD线程0x258地址处: +0x258 SavedApcState : _KAPC_STATE +0x258 SavedApcStateFill : [43] UChar此为备份SavedApcState,用于当线程挂靠到别的进程时,保存当前进程的APC State......
PsExitSpecialApc线程的退出示例
在上一节KiInsertQueueApc中,可以看到有一个特殊的APC,其为PsExitSpecialApc,表示线程退出的APC. if (Apc->NormalRoutine) { /* Normal APC; is it the Thread Termin......
APC的执行KiDeliverApc
APC执行是通过KiDeliverApc实现的,不过该函数是一个内核函数。不过APC分为内核APC和应用层APC。对于内核层的APC直接调用即可,但对于应用层的APC,需要临时进入用户层,执行用户层的APC,执行完成后再进入内核层,再通过内核层返回到应用层原来的返回地址。ReactOS关于其代码如下......
作者信息
我爱内核
Windows驱动开发,网站开发
好好学习,天天向上。
取消
感谢您的支持,我会继续努力的!
扫码支持
扫码打赏,你说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦

您的支持,是我们前进的动力!