KeInsertQueueDpc函数(ntos/ke/dpcobj.c:89)实际上是系统对DPC队列维护的核心函数,其伪代码如下:
以下为引用:
BOOLEAN KeInsertQueueDpc (IN PRKDPC Dpc, IN PVOID SystemArgument1,IN PVOID SystemArgument2) { PKSPIN_LOCK Lock; KIRQL OldIrql;
KeRaiseIrql(HIGH_LEVEL, &OldIrql); // 提升当前IRQL到最高,屏蔽其它中断
PKPRCB = KeGetCurrentPrcb(); // 获取当前处理器控制块
// 通过比较Dpc->Lock是否为空,来判断此DPC对象是否已经被加入到DPC队列; // 如果DPC对象可以被加入到队列,则将当前处理器控制块的DPC自旋锁复制到Dpc->Lock中 if ((Lock = InterlockedCompareExchangePointer(&Dpc->Lock, &Prcb->DpcLock, NULL)) == NULL) { // 更新当前处理器控制块的统计信息 Prcb->DpcCount += 1; Prcb->DpcQueueDepth += 1;
// 更新DPC对象的参数信息 Dpc->SystemArgument1 = SystemArgument1; Dpc->SystemArgument2 = SystemArgument2;
// 根据DPC对象优先级,决定将之加入到DPC队列的头部或尾部 if (Dpc->Importance == HighImportance) InsertHeadList(&Prcb->DpcListHead, &Dpc->DpcListEntry); else InsertTailList(&Prcb->DpcListHead, &Dpc->DpcListEntry);
// 如果当前处理器没有DPC对象活动或DPC中断请求,则进一步判断是否发出DPC中断请求 if (Prcb->DpcRoutineActive == FALSE && Prcb->DpcInterruptRequested == FALSE) { // 如果DPC对象优先级为中高; // 或者DPC队列长度超过阈值MaximumDpcQueueDepth; // 或者DPC请求速率小于阈值MinimumDpcRate if ((Dpc->Importance != LowImportance) || (Prcb->DpcQueueDepth >= Prcb->MaximumDpcQueueDepth) || (Prcb->DpcRequestRate < Prcb->MinimumDpcRate)) { // 满足触发条件,则发出DPC中断请求 Prcb->DpcInterruptRequested = TRUE; KiRequestSoftwareInterrupt(DISPATCH_LEVEL); } } } KeLowerIrql(OldIrql); return (Lock == NULL); }
这里的几个阈值,在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时用于指定处理器 { Processor = Dpc->Number - MAXIMUM_PROCESSORS; Prcb = KiProcessorBlock[Processor]; // 全局唯一的处理器控制块列表
} else { Prcb = KeGetCurrentPrcb(); }
KiAcquireSpinLock(&Prcb->DpcLock); // 使用自旋锁保护处理器控制块中的DPC队列
而在KeInsertQueueDpc函数中判断是否发出DPC中断请求时,也需要做更复杂的逻辑判断。 对DPC对象目标处理器就是当前处理器的情况,可以和前面单处理器时一样处理,直接发送DPC中断请求;但对于DPC对象目标处理器是其他处理器的情况,就必须使用KiIpiSend函数发送IPI(InterProcessor Interrupt)中断,通知目标处理器执行动作。此IPI中断是介于系统掉电中断(POWER_LEVEL)和时钟中断之间的特殊IRQL,专门用于在多处理器情况下协调多个处理器的工作。 此外就是在多处理器情况下,各种对DPC队列的操作都需要用此处理器控制块的DPC队列自旋锁保护起来,避免同步问题。
由此我们可以看到,实际上DPC队列是每个处理器一个的,我们完全可以将某个DPC对象绑定到某个处理器上,实现类似线程亲缘性(Thread Affinity)的效果,优化在多处理器环境下的性能。但这同时也带来一个问题,就是ISR程序可以和DPC回调函数同时被调用,某种程度上也造成了开发复杂度的增加,具体处理方法请参考DDK中相关文档。
Kernel-Mode Driver Architecture/Design Guide/Servicing Interrupts/DPC Objects and DPCs 
说明:本教程来源互联网或网友上传或出版商,仅为学习研究或媒体推广,wanshiok.com不保证资料的完整性。
2/2 首页 上一页 1 2 |