IRP完成例程与KeWaitXxx配合出现的同步问题

崔泽世   2009-6-1 08:51 楼主
我的代码是这样的:
{
   .......
   KeInitializeEvent(&event, NotificationEvent, FALSE);
   IoCopyCurrentIrpStackLocationToNext(Irp);
   IoSetCompletionRoutine(Irp,PacketCompletion,&event,TRUE,TRUE,TRUE);   
   status = IoCallDriver(pTDIH_DeviceExtension->LowerDeviceObject,Irp);
   if(status == STATUS_PENDING)
   {
        KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, NULL);
        status = Irp->IoStatus.Status;
   }
   IoCompleteRequest(Irp, IO_NO_INCREMENT);
   return status;
}

NTSTATUS   
PacketCompletion(   
    IN  PDEVICE_OBJECT  DeviceObject,   
    IN  PIRP            Irp,   
    IN  PVOID           Context   
)   
{   
    if(Irp->PendingReturned)  
            {
                    KeSetEvent((PKEVENT)Context, IO_NO_INCREMENT, FALSE);
            }
    return STATUS_MORE_PROCESSING_REQUIRED;

}


出现的问题是0x000000b8,或者我加了driververifier以后,是0x000000c4,para1是3b,也就是说错误是:
Bug Check 0xB8: ATTEMPTED_SWITCH_FROM_DPC
The ATTEMPTED_SWITCH_FROM_DPC bug check has a value of 0x000000B8. This indicates that an illegal operation was attempted by a delayed procedure call (DPC) routine.
或者
The driver called KeWaitXxx at an IRQL of DISPATCH_LEVEL or higher.
(This is permitted only if the driver already owns the DISPATCHER lock and it passes a timeout value of zero to the routine.)
另外:这种错误是比较随机的,可能运行半个小时都没事,突然就爆发,在虚拟机单核的时候没怎么感觉发生过,但是在真机双核或者虚拟机双核时基本上频繁访问十分钟左右就会蓝屏。请高手们分析一下,给个提示,非常感谢!

回复评论 (7)

等待函数只能在IRQL <= DISPATCH_LEVEL时执行,如果IRQL == DISPATCH_LEVEL,Timeout必须为0才行。
如果你的程序在处理Irp时需要等待,可以在初始化时创建一个系统线程,并自己维护一个队列,在派遣函数中先将Irp标记为pending并放入队列,然后返回,在系统线程中从队列逐一取出Irp来处理。
点赞  2009-6-1 12:18
前一个函数就是Dispatch函数,应该是在PASSIVE_LEVEL,不清楚为什么后来事件激活以后会升至DISPATCH_LEVEL,而且是偶尔;我曾经把TIMEOUT值改为0过,但是会报一个存在CANCEL_ROUTING的情况下,不能调用IoCompleteRequest结束IRP;上面这段例程在http://support.microsoft.com/kb/320275/en-us里有演示,基本上没有更动,我想既然微软专家是这样演示的,在国内出版的windows驱动开发详解里也有这段代码,我想可能原因不是那么单纯。
点赞  2009-6-2 07:21
Dispatch函数的IRQL不是固定的,有时可能是DISPATCH_LEVEL,你可以在程序中取IRQL判断是否等于DISPATCH_LEVEL。
点赞  2009-6-2 13:21
我有用ASSERT来判断,只是觉得不起什么作用,还是排除不掉这个故障,而我无论如何都需要等到事件激活以后再次处理IRP内容。
点赞  2009-6-2 17:06
派遣函数中只有IRQL
点赞  2009-6-2 21:11
非常感谢王老五老兄的解答,其实这个问题在发帖当天就已经解决了,但是用了别的变通的方法,这个疑问还是成立的,所以一直没有结贴,我的最终解决方案依然是在PacketCompletion中释放了我为IRP分配的内存,并恢复了IRP原来的内容,因为都是非分页内存,所以在DISPATCH_LEVEL也不会有什么问题,让我纳闷的依然是,在DISPATCH线程中,IRQL的级别怎么会随便变换的,没有采取任何提升IRQL的动作,只是在一个事件上等待,我是最顶层的驱动,也不是DPC。如果IRQL随机变换的话,微软演示的例程也一样会死机蓝屏,难道大家都在以讹传讹?

下面是微软例程:

Scenario 2: Forward and wait
Use the following code if a driver wants to forward the IRP to a lower driver and wait for it to return so that it can process the IRP. This is frequently done when handling PNP IRPs. For example, when you receive a IRP_MN_START_DEVICE IRP, you must forward the IRP down to the bus driver and wait for it to complete before you can start your device. The Windows XP system has a new function named IoForwardIrpSynchronously that you can use to do this operation easily.
NTSTATUS
DispatchRoutine_2(
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp
    )
{
    KEVENT   event;
    NTSTATUS status;

    KeInitializeEvent(&event, NotificationEvent, FALSE);

    //
    // You are setting completion routine, so you must copy
    // current stack location to the next. You cannot skip a location
    // here.
    //
    IoCopyCurrentIrpStackLocationToNext(Irp);

    IoSetCompletionRoutine(Irp,
                           CompletionRoutine_2,
                           &event,
                           TRUE,
                           TRUE,
                           TRUE
                           );

    status = IoCallDriver(TopOfDeviceStack, Irp);

    if (status == STATUS_PENDING) {
        
       KeWaitForSingleObject(&event,
                             Executive, // WaitReason
                             KernelMode, // must be Kernelmode to prevent the stack getting paged out
                             FALSE,
                             NULL // indefinite wait
                             );
       status = Irp->IoStatus.Status;
    }
   
    // <---- Do your own work here.


    //
    // Because you stopped the completion of the IRP in the CompletionRoutine
    // by returning STATUS_MORE_PROCESSING_REQUIRED, you must call
    // IoCompleteRequest here.
    //
    IoCompleteRequest (Irp, IO_NO_INCREMENT);
    return status;

}
NTSTATUS
CompletionRoutine_2(
    IN PDEVICE_OBJECT   DeviceObject,
    IN PIRP             Irp,
    IN PVOID            Context
    )
{
  if (Irp->PendingReturned == TRUE) {
    //
    // You will set the event only if the lower driver has returned
    // STATUS_PENDING earlier. This optimization removes the need to
    // call KeSetEvent unnecessarily and improves performance because the
    // system does not have to acquire an internal lock.  
    //
    KeSetEvent ((PKEVENT) Context, IO_NO_INCREMENT, FALSE);
  }
  // This is the only status you can return.
  return STATUS_MORE_PROCESSING_REQUIRED;  
}

点赞  2009-6-3 06:42
不同类型的设备和IRP,其可能的IRQL也不同。
点赞  2009-6-3 12:46
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 京公网安备 11010802033920号
    写回复