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

当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)处理;


  1.     switch( MinorFunction)
  2.     {
  3.     case IRP_MN_START_DEVICE:
  4.         status = PnpStartDeviceHandler(fdo,Irp);
  5.         break;
  6.         }

下面是IRP_ MN_START_DEVICE处理函数PnpStartDeviceHandler:


  1. NTSTATUS PnpStartDeviceHandler( IN PDEVICE_OBJECT fdo, IN PIRP Irp)
  2. {
  3.     PDEVICE_EXTENSION dx=(PDEVICE_EXTENSION)fdo->DeviceExtension;
  4.     PIO_STACK_LOCATION IrpStack = IoGetCurrentIrpStackLocation(Irp);
  5.     NTSTATUS status = ForwardIrpAndWait( fdo, Irp);
  6.     if( !NT_SUCCESS(status))
  7.         return CompleteIrp( Irp, status, Irp->IoStatus.Information);
  8.     status=StartDevice(dx,IrpStack->Parameters.StartDevice.AllocatedResourcesTranslated);
  9.     return CompleteIrp( Irp, status, 0);
  10. }

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


  1. NTSTATUS ForwardIrpAndWait( IN PDEVICE_OBJECT fdo, IN PIRP Irp)
  2. {
  3.     PWDM2_DEVICE_EXTENSION dx=(PWDM2_DEVICE_EXTENSION)fdo->DeviceExtension;
  4.     KEVENT event;                    
  5.     KeInitializeEvent( &event, NotificationEvent, FALSE); <--1
  6.     IoCopyCurrentIrpStackLocationToNext(Irp); <--2
  7.     IoSetCompletionRoutine( Irp, (PIO_COMPLETION_ROUTINE) IoCompleteRequest,   (PVOID
  8. &event, TRUE, TRUE, TRUE); <--3
  9.     NTSTATUS status = IoCallDriver( dx->NextStackDevice,Irp); <--4
  10.     if( status==STATUS_PENDING)
  11.     {
  12.       KeWaitForSingleObject( &event, Executive, KernelMode, FALSE, NULL); <--5
  13.       status = Irp->IoStatus.Status; <--6
  14.     }
  15. }

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

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

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

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

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

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

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

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

一旦我们调用了IoCallDriver,我们就放弃了IRP的控制权,直到某些运行在任意线程上下文中的代码调用IoCompleteRequest通知该IRP完成,IoCompleteRequest将调用我们的完成例程。完成例程特别简单:


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

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

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


  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值