异步过程调用

异步过程调用(asynchronous procedure call)是函数(过程)在特定线程中被异步执行。在Microsoft Windows操作系统中, APC是一种并发机制,用于异步IO或者定时器[1]

分类

Windows NT操作系统中有3种APC:

  • 内核模式特殊APC:相应的APC函数为内核函数。在IRQL =APC_LEVEL级上有可调度的活动时,执行此类APC。会抢先所有的用户模态以及IRQL = PASSIVE_LEVEL的内核模态下的代码的执行。[2]
  • 内核模式常规APC:在所有的内核模式特殊APC执行完毕后,内核模式常规APC在IRQL = PASSIVE_LEVEL下开始执行。会抢先所有的用户模式代码的执行。用于文件系统。
  • 用户模式APC:是指相应的 APC 函数位于用户空间、在用户空间执行。线程处于alertable wait状态该APC才可以被调度执行。用户模式下调用系统API如SleepEx, SignalObjectAndWait,WaitForSingleObjectEx, WaitForMultipleObjectsEx, MsgWaitForMultipleObjectEx等,可以使线程进入alertable状态。这些API函数最终都是调用了内核中的KeWaitForSingleObject, KeWaitForMultipleObjects,KeWaitForMutexObject, KeDelayExecutionThread, KeTestAlertThread等函数。线程在alertable wait状态所有内核模式API执行完毕,返回用户模式时,内核转去执行APC,完成后再继续线程的原来执行。

API

  • APC对象:为struct KAPC类型。每个APC对象必须要包含一个KernelRoutine函数指针,当这个APC被操作系统的APC dispatcher执行时,该例程首先被执行。用户模式APCs必须包含一个NormalRoutine函数指针,所指的函数在用户内存区域。内核模式常规APC也必须包含一个NormalRoutine,但运行在内核模式(即_KAPC.ApcMode==KernelMode)。内核模式特殊APC的NormalRoutine为空。任意类型的APC都可以定义一个RundownRoutine,所指函数在内核内存区域,当系统需要释放APC队列的内容时(例如线程退出时)被调用。
  • 每个线程有两个APC队列,分为用户APC队列和内核APC队列。
  • QueueUserAPC: 应用程序把APC放入一个线程的用户模式APC队列中;
  • KeInitializeApc 处理异步IO的设备驱动程序用这个函数来初始化APC对象(为struct KAPC类型)。如果函数参数NormalRoutine为NULL,那么生成的是内核模式特殊APC对象;如果参数NormalRoutine为NULL且参数ApcMode的值是KernelMode,那么生成的是内核模式常规APC对象;否则生成用户模式APC对象。
  • KeInsertQueueApc 把完成初始化的APC对象存放到目标线程的APC队列中
  • KiInsertQueueApc 实际完成插入到队列中的操作
  • KiDeliverApc 即操作系统APC派发器子程序。投递一个挂起的(pending)APC到目标线程。KiDeliverApc每处理完一个User APC就把UserApcPending清零,所以User APCs在返回用户空间时还是只能投递一次.
  • KiInitializeUserApc:在从内核态返回到用户态以执行用户模式APC时,修改环境以准备好由KeUserApcDispatcher调用User APC回调函数.

参考文献

  1. ^ MSDN article: "Asynchronous Procedure Calls". [2016-08-19]. (原始内容存档于2016-09-13). 
  2. ^ Windows的中断请求级别由低到高是0-31。其中PASSIVE_LEVEL=0,为用户态与大部分内核态操作;APC_LEVEL为异步过程调用与缺页;DISPATCH_LEVEL=2为线程调度器与deferred procedure calls。