延迟过程调用

延迟过程调用(DPC)是Microsoft Windows操作系统的机制,允许高优先级任务(如中断处理程序英语interrupt handler)延迟所需的低优先级任务稍后执行。这使得设备驱动程序与其他低层事件消费者更快地执行其处理的高优先级部分,调度非关键的附件处理稍后以较低优先级执行。

DPC是通过DPC对象实现的。当设备驱动程序或其他内核态程序发出DPC请求时,操作系统内核创建DPC对象,投寄到DPC队列尾部。多核处理器的每个内核有自己的DPC队列。当Windows操作系统的IRQL降低到Dispatch/DPC级,操作系统检查DPC队列,逐个执行挂起的DPC,直至队列为空或者发生IRQL更高的中断。

例如,当时钟中断發生时,时钟中断服务程序通常增加当前线程的计数器以计算当前线程的总执行时间,把它的时间片减1。当时间片减少到0,线程调度器将被唤醒去选择下一个在当前处理器内核上运行的线程并做线程上下文切换。时钟中断服务程序运行在非常高的IRQL上,它应该在稍后的IRQL下降到低级时才去执行线程调度程序。因此时钟中断服务程序请求一个DPC对象并把这个对象放在DPC队列尾部,当处理器的IRQL下降到DPC/Dispatch级别时,回去执行DPC队列中的任务。

当流音视频工作时,使用DPC处理流输入到缓冲区的音频。如果另外一个DPC执行了很长时间并且另一次中断产生了新的缓冲区数据,在第一块数据被处理前,会发生信号缺失英语Dropout (electronics)结果。[1]

技术细节 编辑

具体说,CPU的每个内核对应的_KPRCB数据结构中,有两个DPC队列:

  • (普通)DPC队列:普通的DPC可以在任何一个线程环境中运行
  • 线程化DPC队列:这种DPC只能在内核启动时系统创建的一个叫做DPC的线程中运行。该线程工作在passive层,但有最高的线程优先级(Realtime级)。因此,CPU一旦工作在passive级,最先执行的就是线程化DPC例程中的代码。

DPC执行例程一般较大,为避免当前线程的运行栈溢出,DPC例程使用每个CPU专门的DPC运行栈。

DPC有三种优先级:

  • low:入队时放在队列头部,仅当队列长度超限或者DPC请求率太低,才会被执行。
  • medium:默认值。入队时放在队列尾部,总是被投递(即执行)。
  • high:入队时放在队列头部,总是被投递(即执行)。

DPC对象是Windows内核对象之一,其数据结构定义为:

Typedef struct _KDPC
{
 UCHAR Type;//DpcObject或者ThreadedDpcObject类型
 UCHAR Importance;//High,medium,low
 USHORT Number;//CPU的哪个核上
 LIST_ENTRY DpcListEntry;//链表结构
 PKDEFERRED_ROUTINE DeferredRoutine;//DPC例程
 PVOID DeferredContext;//执行DPC时的上下文 
 PVOID SystemArgument1;//执行DPC时的参数1 
 PVOID SystemArgument2;//执行DPC时的参数2 
 Volatile PVOID DpcData;//指向CPU的核的数据结构_KPRCB中的DpcData成员
}KDPC

DPC对象入队时,内核会请求一个在Dispatch/DPC级的软件中断。当IRQL降低到APC或者PASSIVE级时,会投递一个APC对象并把IRQL升到DPC级。此时DPC例程会在任何任意进程的运行环境中,因此DPC例程通常只使用内核地址空间。

设备驱动编程 编辑

设备驱动编程中使用下述API来管理延迟过程调用:

  • IolnitimizeDpcRequest:初始化设备对象的内置DPC对象,同时注册一个DPC例程。由KeInitializeDpc将DPC对象与DPC例程、驱动程序创建的设备对象联系起来。
  • IoRequestDpc:请求一个DPC调用,由KeInsertQueueDpc函数将DPC对象排队。如果在DPC例程运行前,该设备又生成一个中断,系统内核将忽略随后入队的任何DPC对象。即每个设备只能有一个DPC对象。
  • KeSetTargetProcessorDpc:把一个DPC对象送入特定处理器的DPC队列
  • KeSetImportanceDpc:设置DPC对象的优先级
  • KiRetireDpcList:逐个执行DPC队列中的所有对象

参考文献 编辑

  1. ^ Ute Eberhardt. DPC Latency Checker. Thesycon.de. 27 June 2012 [2012-11-07]. (原始内容存档于2012年10月30日).