⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄

📁 兼容内核漫谈 适合想将Windows上的程序移植到其它平台上的朋友研究查看
💻
📖 第 1 页 / 共 4 页
字号:
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)
{
    . . . . . .

    /* Set up the basic APC Structure Data */
    RtlZeroMemory(Apc, sizeof(KAPC));
    Apc->Type = ApcObject;
    Apc->Size = sizeof(KAPC);
   
    /* Set the Environment */
    if (TargetEnvironment == CurrentApcEnvironment) {
       
        Apc->ApcStateIndex = Thread->ApcStateIndex;
       
    } else {
       
        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, in which case we use KernelMode and no Context */
    if (ARGUMENT_PRESENT(NormalRoutine)) {
       
        Apc->ApcMode = Mode;
        Apc->NormalContext = Context;
       
    } else {
       
        Apc->ApcMode = KernelMode;
    } 
}[/code]

    这段代码本身很简单,但是有几个问题需要结合前面NtQueueApcThread()的代码再作些说明。
    首先,从NtQueueApcThread()传下来的KernelRoutine是KiFreeApcRoutine(),顾名思义这是在为将来释放PKAPC数据结构做好准备,而RundownRoutine是NULL。
    其次,参数TargetEnvironment说明要求挂入哪一种环境下的APC队列。实际传下来的值是OriginalApcEnvironment,表示是针对原始环境、即当前线程所属(而不是所挂靠)进程的。注意代码中所设置的是Apc->ApcStateIndex、即PKAPC数据结构中的ApcStateIndex字段,而不是KTHREAD结构中的ApcStateIndex字段。另一方面,ApcStateIndex的值只能是OriginalApcEnvironment或AttachedApcEnvironment,如果所要求的是CurrentApcEnvironment就要从Thread->ApcStateIndex获取当前的环境值。
    最后,APC请求的模式Mode是UserMode。但是有个例外,那就是:如果指针NormalRoutine为0,那么实际的模式变成了KernelMode。这是因为在这种情况下没有用户空间APC函数可以执行,唯一将得到执行的是KernelRoutine,在这里是KiFreeApcRoutine()。这里的宏操作ARGUMENT_PRESENT定义为:

[code]#define ARGUMENT_PRESENT(ArgumentPointer) \
  ((BOOLEAN) ((PVOID)ArgumentPointer != (PVOID)NULL))[/code]

    回到NtQueueApcThread()代码中,下一步就是根据Apc->ApcStateIndex、Apc->Thread、和Apc->ApcMode把准备好的KAPC结构挂入相应的队列。根据APC请求的具体情况,有时候要插在队列的前头,一般则挂在队列的尾部。限于篇幅,我们在这里就不看KeInsertQueueApc()的代码了;虽然这段代码中有一些特殊的处理,但都不是我们此刻所特别关心的。
    如果跟Linux的Signal机制作一类比,那么NtQueueApcThread()相当于设置Signal处理函数(或中断服务程序)。在Linux里面,Signal处理函数的执行需要受到某种触发,例如收到了别的线程或某个内核成分发来的信号;而执行Signal处理函数的时机则是在CPU从内核返回目标线程的用户空间程序的前夕。可是Windows的APC机制与此有所不同,一般来说,只要把APC请求挂入了队列,就不再需要触发,而只是等待执行的时机。对于用户APC请求,这时机同样也是在CPU从内核返回目标线程用户空间程序的前夕(对于内核APC则有所不同)。所以,在某种意义上,把一个APC请求挂入队列,就同时意味着受到了触发。对于系统调用NtQueueApcThread(),我们可以理解为是把APC函数的设置与触发合在了一起。而对于异步的文件读写,则APC函数的设置与触发是分开的,内核先把APC函数记录在别的数据结构中,等实际的文件读写完成以后才把APC请求挂入队列,此时实际上只是触发其运行。不过那已是属于设备驱动框架的事了。所以,一旦把APC请求挂入队列,就只是等待执行时机的问题了。从这个意义上说,“异步过程调用”还真不失为贴切的称呼。

    下面就来看执行APC的时机,那是在(系统调用、中断、或异常处理之后)从内核返回用户空间的途中。

[code]_KiServiceExit:

    /* Get the Current Thread */
    cli
    movl %fs:KPCR_CURRENT_THREAD, %esi
   
    /* Deliver APCs only if we were called from user mode */
    testb $1, KTRAP_FRAME_CS(%esp)
    je KiRosTrapReturn
   
    /* And only if any are actually pending */
    cmpb $0, KTHREAD_PENDING_USER_APC(%esi)
    je KiRosTrapReturn
   
    /* Save pointer to Trap Frame */
    movl %esp, %ebx
   
    /* Raise IRQL to APC_LEVEL */
    movl $1, %ecx
    call @KfRaiseIrql@4
   
    /* Save old IRQL */
    pushl %eax
   
    /* Deliver APCs */
    sti
    pushl %ebx
    pushl $0
    pushl $UserMode
    call _KiDeliverApc@12
    cli
   
    /* Return to old IRQL */
    popl %ecx
    call @KfLowerIrql@4
    . . . . . .[/code]

    这是内核中处理系统调用返回和中断/异常返回的代码。在返回前夕,这里先通过%fs:KPCR_CURRENT_THREAD取得指向当前线程的ETHREAD(从而KTHREAD)的指针,然后依次检查:
    ● 即将返回的是否用户空间。
    ● 是否有用户APC请求正在等待执行(KTHREAD_PENDING_USER_APC是        ApcState.KernelApcPending在KTHREAD数据结构中的位移)。
    要是通过了这两项检查,执行针对当前线程的APC请求的时机就到了,于是就调用KiDeliverApc()去“投递”APC函数,这跟Linux中对Signal的处理又是十分相似的。注意在调用这个函数的前后还分别调用了KfRaiseIrql()和KfLowerIrql(),这是为了在执行KiDeliverApc()期间让内核的“中断请求级别”处于APC_LEVEL,执行完以后再予恢复。我们现在暂时不关心“中断请求级别”,以后会回到这个问题上。
    前面讲过,KTHREAD中有两个KAPC_STATE数据结构,一个是ApcState,另一个是SavedApcState,二者都有APC队列,但是要投递的只是ApcState中的队列。
    注意在call指令前面压入堆栈的三个参数,特别是首先压入堆栈的%ebx,它指向(系统空间)堆栈上的“中断现场”、或称“框架”,即CPU进入本次中断或系统调用时各寄存器的值,这就是下面KiDeliverApc()的调用参数TrapFrame。
    下面我们看KiDeliverApc()的代码。

[code][KiDeliverApc()]

VOID
STDCALL
KiDeliverApc(KPROCESSOR_MODE DeliveryMode,
             PVOID Reserved,
             PKTRAP_FRAME TrapFrame)
{
  PKTHREAD Thread = KeGetCurrentThread();
  . . . . . .

  ASSERT_IRQL_EQUAL(APC_LEVEL);

  /* Lock the APC Queue and Raise IRQL to Synch */
  KeAcquireSpinLock(&Thread->ApcQueueLock, &OldIrql);

  /* Clear APC Pending */
  Thread->ApcState.KernelApcPending = FALSE;

  /* Do the Kernel APCs first */
  while (!IsListEmpty(&Thread->ApcState.ApcListHead[KernelMode])) {
    /* Get the next Entry */
    ApcListEntry = Thread->ApcState.ApcListHead[KernelMode].Flink;
    Apc = CONTAINING_RECORD(ApcListEntry, KAPC, ApcListEntry);
   
    /* Save Parameters so that it's safe to free the Object in Kernel Routine*/
    NormalRoutine   = Apc->NormalRoutine;
    KernelRoutine   = Apc->KernelRoutine;
    NormalContext   = Apc->NormalContext;
    SystemArgument1 = Apc->SystemArgument1;
    SystemArgument2 = Apc->SystemArgument2;
   
    /* Special APC */
    if (NormalRoutine == NULL) {
      /* Remove the APC from the list */
      Apc->Inserted = FALSE;
      RemoveEntryList(ApcListEntry);
     
      /* Go back to APC_LEVEL */
      KeReleaseSpinLock(&Thread->ApcQueueLock, OldIrql);
     
      /* Call the Special APC */
      DPRINT("Delivering a Special APC: %x\n", Apc);
      KernelRoutine(Apc, &NormalRoutine, &NormalContext,
                      &SystemArgument1, &SystemArgument2);

      /* Raise IRQL and Lock again */
      KeAcquireSpinLock(&Thread->ApcQueueLock, &OldIrql);
     
    } else {
     
      /* Normal Kernel APC */
      if (Thread->ApcState.KernelApcInProgress || Thread->KernelApcDisable)
      {
         /*
         * DeliveryMode must be KernelMode in this case, since one may not
         * return to umode while being inside a critical section or while
         * a regular kmode apc is running (the latter should be impossible btw).
         * -Gunnar
         */
         ASSERT(DeliveryMode == KernelMode);

         KeReleaseSpinLock(&Thread->ApcQueueLock, OldIrql);
         return;

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -