以文本方式查看主题 - 计算机科学论坛 (http://bbs.xml.org.cn/index.asp) -- 『 操作系统原理 』 (http://bbs.xml.org.cn/list.asp?boardid=63) ---- [转帖]Win32核心DPC设计思想和实现思路浅析 (http://bbs.xml.org.cn/dispbbs.asp?boardid=63&rootid=&id=50129) |
-- 作者:longshentailang -- 发布时间:7/17/2007 8:49:00 PM -- [转帖]Win32核心DPC设计思想和实现思路浅析 x86架构设计在上是基于中断思想的,因而从DOS到Win32,操作系统中大量使用中断的概念来表达异步操作的行为。但与DOS下独占的情况不同,Win32下需要由系统对多任务进行调度,因此中断响应代码必须尽可能地简单,并且尽快的将控制权交还给系统。虽然这样一来系统调度的响应速度和实现过程方便了,但还是有很多功能需要在中断响应中完成。为此,Win32核心提供了DPC(Deferred Procedure Call)和APC(Asynchronous Procedure Call)两个IRQL特殊的软件中断级别,用于实现延迟和异步的过程调用。 从IRQL分层来说,DPC和APC是介于较高级别的设备中断和最低级别的Passive中断之间,由操作系统用于完成特殊方法调用的中断级别。与处理硬件操作的设备中断和更高级别的时钟、处理器中断不同,这两级中断纯粹是为了实现功能调用异步性而设计实现的,因此操作系统本身也对它们具有很强的依赖型。 APC这里暂且不讨论,以后有机会再写篇文章专门讨论 :) DPC在功能上可以理解为ISR(Interrupt Service Routine)的一部分。只是因为ISR为了尽量简单和返回控制权给操作系统,而将一部分功能剥离出来放入相应DPC中,延迟调用。因为DPC的 IRQL仅在APC和Passive中断之上,所以系统可以从容地处理完高级别的中断后,再在DPC一级慢慢处理积累起来的相对并不那么紧急功能。 DPC 在使用上可以理解为一个回调函数的封装对象。系统本身或者设备驱动程序,在合适的地方如设备驱动程序的AddDevice函数或DispatchPnP函数处理IRP_MN_START_DEVICE请求时,初始化一个DPC对象;在ISR中判断是否需要进一步处理中断,是则请求将DPC对象插入到系统 DPC队列中;系统处理完高IRQL后,会在IRQL DISPATCH_LEVEL级别慢慢处理DPC队列中的DPC对象;每个DPC对象封装的回调函数,会使用同时封装的调用参数,被系统调用,完成在 ISR中来不及完成的工作;如果需要进一步的工作,还可以继续请求插入DPC对象到DPC队列中。 DPC对象从最终用户角度有两种:DpcForIsr和CustomDPC。前者是与设备驱动对象(Device Object)绑定的;后者则由驱动自行维护。但从实现上来说,只有一种DPC对象存在,DpcForIsr所涉及的维护函数,实际上都是对 CustomDPC的一个封装而已。 我们首先来看看初始化DPC对象的实现。KeInitializeDpc函数(ntos\ke\dpcobj.c:39)完成具体的DPC对象的初始化,实际上就是填充一个内存结构KDPC(ntos\inc\ntosdef.h:331)。 以下为引用: // typedef struct _KDPC { Type 表示此内核对象的类型,在KOBJECTS枚举类型(ntos\inc\ke.h:122)中定义,缺省为 DpcObject = 0x13。此外WinXP/2003新增了一种ThreadedDpcObject = 0x18 了解了KDPC对象的结构,实际上维护代码就非常简单了。KeInitializeDpc函数将KDPC对象结构初始化为初值;IoInitializeDpcRequest函数则只是对KeInitializeDpc函数的一个简单包装,如下 以下为引用: #define IoInitializeDpcRequest( DeviceObject, DpcRoutine ) ( KeInitializeDpc( &(DeviceObject)->Dpc, (PKDEFERRED_ROUTINE) (DpcRoutine), (DeviceObject) ) ) 注意WinXP/2003下实际上KeInitializeDpc函数和KeInitializeThreadedDpc函数都是由一个KiInitializeDpc函数完成具体工作的,只是传递的最后一个参数定义的对象类型不同。 KeInsertQueueDpc函数(ntos\ke\dpcobj.c:89)实际上是系统对DPC队列维护的核心函数,其伪代码如下: 以下为引用: BOOLEAN KeInsertQueueDpc (IN PRKDPC Dpc, IN PVOID SystemArgument1,IN PVOID SystemArgument2) KeRaiseIrql(HIGH_LEVEL, &OldIrql); // 提升当前IRQL到最高,屏蔽其它中断 PKPRCB = KeGetCurrentPrcb(); // 获取当前处理器控制块 // 通过比较Dpc->Lock是否为空,来判断此DPC对象是否已经被加入到DPC队列; // 更新DPC对象的参数信息 // 根据DPC对象优先级,决定将之加入到DPC队列的头部或尾部 // 如果当前处理器没有DPC对象活动或DPC中断请求,则进一步判断是否发出DPC中断请求 这里的几个阈值,在KiInitializeKernel函数(ntos\ke\i386\kernlini.c:246)中,根据全局变量 KiMaximumDpcQueueDepth、KiMinimumDpcRate和KiAdjustDpcThreshold确定。而这几个全局变量可以通过注册表项(HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\kernel\)下的DpcQueueDepth、MinimumDpcRate和AdjustDpcThreshold三个键值来设置。具体的设置方法,请参考MSDN以及性能计数器的Processor\% DPC Time等动态指数。 而处理与驱动绑定的DPC对象的IoRequestDpc函数只是KeInsertQueueDpc函数的一个简单包装。 以下为引用: #define IoRequestDpc( DeviceObject, Irp, Context ) ( KeInsertQueueDpc( &(DeviceObject)->Dpc, (Irp), (Context) ) ) 与KeInsertQueueDpc函数对应的KeRemoveQueueDpc函数(ntos\ke\dpcobj.c:272)实际上只是完成简单的将DPC对象从DPC队列中删除的功能。 最后对DPC对象属性进行修改的KeSetImportanceDpc函数(ntos\ke\dpcobj.c:367)和 KeSetTargetProcessorDpc函数(ntos\ke\dpcobj.c:401)实际上都是直接修改DPC对象结构的相应域。 KDPC::Number大于MAXIMUM_PROCESSORS = 32时,用于指定DPC对象的目标CPU。如调用KeSetTargetProcessorDpc(pKDpc, 2)后,pKDpc = MAXIMUM_PROCESSORS + 2。 在了解了DPC对象和DPC队列的大致维护函数功能后,我们来看看稍微复杂一些的在多处理器下DPC队列的维护流程。 前面提到KDPC::Number指定了DPC对象所用的处理器号,因此在KeInsertQueueDpc函数开始获取处理器控制块时,需要判断Number是否指向一个处理器,并从全局处理器控制块列表中获取相应的处理器控制块,为代码如下: 以下为引用: if (Dpc->Number >= MAXIMUM_PROCESSORS) // Number大于MAXIMUM_PROCESSORS时用于指定处理器 } KiAcquireSpinLock(&Prcb->DpcLock); // 使用自旋锁保护处理器控制块中的DPC队列 而在KeInsertQueueDpc函数中判断是否发出DPC中断请求时,也需要做更复杂的逻辑判断。 此外就是在多处理器情况下,各种对DPC队列的操作都需要用此处理器控制块的DPC队列自旋锁保护起来,避免同步问题。 由此我们可以看到,实际上DPC队列是每个处理器一个的,我们完全可以将某个DPC对象绑定到某个处理器上,实现类似线程亲缘性(Thread Affinity)的效果,优化在多处理器环境下的性能。但这同时也带来一个问题,就是ISR程序可以和DPC回调函数同时被调用,某种程度上也造成了开发复杂度的增加,具体处理方法请参考DDK中相关文档。 Kernel-Mode Driver Architecture\Design Guide\Servicing Interrupts\DPC Objects and DPCs |
W 3 C h i n a ( since 2003 ) 旗 下 站 点 苏ICP备05006046号《全国人大常委会关于维护互联网安全的决定》《计算机信息网络国际联网安全保护管理办法》 |
31.250ms |