分享

IRP_MN_START_DEVICE分发例程中的前进和等待IRP总结

 tuohuang0303 2011-04-28

 当PnP管理器检测到硬件时,它首先参考注册表以了解有哪些过滤器驱动程序将管理该硬件。如果必要(某些驱动程序可能因为其它硬件的需要已经被系统装入)它将装入这些驱动程序,并调用它们的AddDevice函数。最后AddDevice函数创建设备对象并连入设备堆栈。此后,PnP管理器将为所有设备驱动程序分配I/O资源。

      一旦资源分配确定,PnP管理器通过向每个设备发送一个带IRP_MN_START_DEVICE副功能码的PnP请求来通知设备。通常过滤器驱动程序对这个IRP不感兴趣,所以它们使用DefaultPnpHandler方式把请求向下传。而功能驱动程序正好相反,它需要在这个IRP上做大量工作,包括分配并配置额外的软件资源以及为设备操作做准备。这个工作需要在PASSIVE_LEVEL级上进行,并在低层驱动程序处理完该IRP后完成。

      在PnP处理函数中分发IRP_ MN_START_DEVICE这个PnP给函数PnpStartDeviceHandler(fdo,Irp)处理;

    switch( MinorFunction) 
    { 
        case IRP_MN_START_DEVICE: 
        status = PnpStartDeviceHandler(fdo,Irp); 
        break; 
     }
下面是IRP_ MN_START_DEVICE处理函数PnpStartDeviceHandler:

NTSTATUS PnpStartDeviceHandler( IN PDEVICE_OBJECT fdo, IN PIRP Irp) 

    PDEVICE_EXTENSION dx=(PDEVICE_EXTENSION)fdo->DeviceExtension; 
    PIO_STACK_LOCATION IrpStack = IoGetCurrentIrpStackLocation(Irp); 
    NTSTATUS status = ForwardIrpAndWait( fdo, Irp); 
    if( !NT_SUCCESS(status)) 
        return CompleteIrp( Irp, status, Irp->IoStatus.Information); 
    status=StartDevice(dx,IrpStack->Parameters.StartDevice.AllocatedResourcesTranslated); 
    return CompleteIrp( Irp, status, 0); 
}

在PnpStartDeviceHandler处理函数中我们可以看到有一个ForwardIrpAndWait函数,这个函数是

为了在下传IRP_MN_START_DEVICE请求后再获得控制,派遣例程需要等待一个内核事件,该事件最终由低层驱动程序对IRP的完成操作来通知。因此,写了这一个辅助函数来执行这个前进和等待机制,该函数原形如下:


NTSTATUS ForwardIrpAndWait( IN PDEVICE_OBJECT fdo, IN PIRP Irp) 

    PWDM2_DEVICE_EXTENSION dx=(PWDM2_DEVICE_EXTENSION)fdo->DeviceExtension; 
    KEVENT event;                     
    KeInitializeEvent( &event, NotificationEvent, FALSE); <--1 
    IoCopyCurrentIrpStackLocationToNext(Irp); <--2 
    IoSetCompletionRoutine( Irp, (PIO_COMPLETION_ROUTINE) IoCompleteRequest,   (PVOID 
&event, TRUE, TRUE, TRUE); <--3 
    NTSTATUS status = IoCallDriver( dx->NextStackDevice,Irp); <--4 
    if( status==STATUS_PENDING) 
    { 
      KeWaitForSingleObject( &event, Executive, KernelMode, FALSE, NULL); <--5 
      status = Irp->IoStatus.Status; <--6 
    } 
}

1   KeInitializeEvent(event, EventType, initialstate)这个函数的解释如下:

event是事件对象的地址。EventType是一个枚举值,可以为NotificationEvent或SynchronizationEvent。通知事件(notification event)有这样的特性,当它进入信号态后,它将一直处于信号态直到你明确地把它重置为非信号态。此外,当通知事件进入信号态后,所有在该事件上等待的线程都被释放。这与用户模式中的手动重置事件相似。而对于同步事件(synchronization event),只要有一个线程被释放,该事件就被重置为非信号态。这又与用户模式中的自动重置事件相同。而KeWaitXxx函数在同步事件对象上执行的附加动作就是把它重置为非信号态。最后的参数是布尔量,为TRUE表示事件的初始状态为信号态,为FALSE表示事件的初始状态为非信号态。

我们创建一个内核事件对象。KeInitializeEvent必须在PASSIVE_LEVEL级上被调用。幸运的是,PnP请求总是在PASSIVE_LEVEL上发送,所以正好符合这种需求。事件对象本身必须占用非分页内存。另外,在大多数情况下,你也可以认为执行堆栈也是非分页的。

2  由于我们要安装一个完成例程,所以必须向下一层驱动程序复制堆栈参数。

3  指定一个完成例程以便我们能知道下层驱动程序何时完成该IRP。我们应该等待完成操作发生,所以必须确保我们的完成例程被调用。这就是为什么我把三个标志参数都指定为TRUE,它们指出我们希望在IRP正常完成、遇到错误、被取消这三种情况下都调用OnRequestComplete。完成例程的上下文参数是event对象的地址。

4   IoCallDriver调用下一层驱动程序,可以是一个低层过滤器驱动程序或是PDO驱动程序本身。PDO驱动程序将执行某些处理,或者是立即完成该请求,或者返回STATUS_PENDING。

5  如果IoCallDriver返回STATUS_PENDING,我们都将调用KeWaitForSingleObject在我们以前建立的内核事件上永远等待。当下层驱动程序完成该IRP并把事件置成信号态时,我们的完成例程将再次获得控制。

6  这里,我们捕获IRP的最终状态并返回给我们的调用者。

一旦我们调用了IoCallDriver,我们就放弃了IRP的控制权,直到某些运行在任意线程上下文中的代码调用

IoCompleteRequest通知该IRP完成,IoCompleteRequest将调用我们的完成例程。完成例程特别简单:


NTSTATUS OnRequestComplete(PDEVICE_OBJECT fdo, PIRP Irp, PKEVENT pev)  
{  
KeSetEvent(pev, 0, FALSE); <--1  
return STATUS_MORE_PROCESSING_REQUIRED; <--2  

1.  我们把阻塞ForwardAndWait的事件置成信号态。

2.  通过返回STATUS_MORE_PROCESSING_REQUIRED,我们停止了I/O堆栈的回卷处理。此时,上层过滤器驱动程序安装的任何完成例程都得不到调用,并且I/O管理器将停止在该IRP上的工作。这种情形就象根本没有调用过IoCompleteRequest一样,当然,某些已经调用过的低级完成例程除外。在这一时刻,该IRP将处于一个中间状态,但我们的ForwardAndWait例程将再次获得该IRP的所有权。


文章出处:飞诺网(www.):http://dev./course/3_program/c++/cppjs/2008827/137774_7.html

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多