异步过程调用(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,完成后再继续线程的原来执行。
- 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回调函数.
Windows的中断请求级别由低到高是0-31。其中PASSIVE_LEVEL=0,为用户态与大部分内核态操作;APC_LEVEL为异步过程调用与缺页;DISPATCH_LEVEL=2为线程调度器与deferred procedure calls。