我的设备不见了。为什么我仍然收到 IRP?

Updated: September 4, 2005

WDM 驱动程序在处理设备删除 IRP 并释放驱动程序分配的内存后可能接收到附加的 IRP。在处理附加的 IRP 时试图引用已经释放的内存会导致系统崩溃。

驱动程序能够接收已删除设备的 IRP,这有两个原因:

在设备被删除后,另一个组件可以发送 I/O。要发送一个 IRP,组件获取目标设备或文件的指针并去除该设备对象上的引用(或者 I/O 管理器代表组件去除引用)。引用可以确保目标设备或文件对象的持续性,从而目标驱动程序可以访问设备对象和设备扩展。但是,除非组件已经在目标设备上注册了即插即用通知,否则它不能确定设备是否仍然存在。

在设备删除请求之前发送的 I/O 请求可能在目标驱动程序处理设备删除请求之后到达。这种情况是否发生取决于哪个组件在发送 I/O、目标驱动程序在设备堆栈中的位置以及为设备挂起的其他 I/O 请求。

对目标驱动程序而言,删除锁是安全 IRP 处理的关键部分。驱动程序通常在设备扩展中分配删除锁,并在 AddDevice 例程中对其进行初始化。此后,每次启动 I/O 操作时,驱动程序调用 IoAcquireRemoveLock 从它的调度例程获得删除锁,并在每次完成 I/O 操作时调用 IoReleaseRemoveLock 来释放它。删除锁维护一个引用计数,并且可以递归获取。

驱动程序何时应该调用 IoReleaseRemoveLock 取决于它如何处理 IRP:通过将其传递给下一层驱动程序(不设置完成例程)、通过完成 IRP 而不将其传递给下一层驱动程序或者通过向下传递 IRP 并设置一个完成例程。

如果驱动程序将 IRP 传递给下一层驱动程序并且不设置 IoCompletion 例程,那么驱动程序应该在调用 IoCallDriver 之后调用 IoReleaseRemoveLock 来向下传递 IRP。例如:

NTSTATUS MyDispatchRoutine ( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp ) { PDEVICE_EXTENSION   devExt; NTSTATUS    status;
    
devExt = (PDEVICE_EXTENSION) DeviceObject->DeviceExtension;
      
status = IoAcquireRemoveLock (&devExt->RemoveLock, Irp); if (!NT_SUCCESS (status)) { // maybe device is being removed.Irp->IoStatus.Information = 0; Irp->IoStatus.Status = status; IoCompleteRequest (Irp, IO_NO_INCREMENT); return status; }

// Do request-specific processing . . . 

// Pass down the IRP and release the lock.IoSkipCurrentIrpStackLocation (Irp); status = IoCallDriver (devExt->NextLowerDriver, Irp); IoReleaseRemoveLock (&devExt->RemoveLock, Irp); return status; }

如果驱动程序完成 IRP 并且不将其传递给下一层驱动程序,那么驱动程序应该在调用 IoCompleteRequest 之后调用 IoReleaseRemoveLock,如下例所示:

NTSTATUS MyDispatchRoutine ( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp ) { PDEVICE_EXTENSION   devExt; NTSTATUS    status;
    
devExt = (PDEVICE_EXTENSION) DeviceObject->DeviceExtension;
      
status = IoAcquireRemoveLock (&devExt->RemoveLock, Irp); if (!NT_SUCCESS (status)) { // maybe device is being removed.Irp->IoStatus.Information = 0; Irp->IoStatus.Status = status; IoCompleteRequest (Irp, IO_NO_INCREMENT); return status; }

// Do request-specific processing . . . 

// Request-specific processing is done.Complete the IRP // and release the lock.Irp->IoStatus.Status = status; IoCompleteRequest (Irp, IO_NO_INCREMENT ); IoReleaseRemoveLock (&devExt->RemoveLock, Irp); return status; }

如果驱动程序将 IRP 传递给下一层驱动程序并设置一个 IoCompletion 例程,那么驱动程序将从 IoCompletion 例程调用 IoReleaseRemoveLock,如下所示:

NTSTATUS MyCompletionRoutine ( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp, IN PVOID Context ) { PDEVICE_EXTENSION   data; UNREFERENCED_PARAMETER (DeviceObject);

data = (PDEVICE_EXTENSION) Context;

IoReleaseRemoveLock (&data->RemoveLock, Irp); return STATUS_SUCCESS; }

当处理一个设备删除请求 (IRP_MN_REMOVE_DEVICE) 时,驱动程序通过调用 IoReleaseRemoveLockAndWait 来释放在其 DispatchPnP 例程中获取的删除锁。这个调用直到与删除锁关联的引用计数达到零时才返回,这表示删除锁的所有其他持有者都已经被释放。在 IoReleaseRemoveLockAndWait 返回之后,驱动程序将 IRP 沿其设备堆栈向下传递(如有必要),调用 IoDetachDevice 来从设备堆栈中删除其设备对象,然后释放在其 AddDevice 例程中分配的资源(例如池内存)。最后,驱动程序调用 IoDeleteDevice 来标记要删除的设备对象。例如:

// Wait for all outstanding requests to complete IoReleaseRemoveLockAndWait(&devExt->RemoveLock, Irp);

// Pass the remove IRP to the next lower driver Irp->IoStatus.Status = STATUS_SUCCESS; IoSkipCurrentIrpStackLocation(Irp); status = IoCallDriver(devExt->NextLowerDriver, Irp);

//Detach from the stack, free resources, and //  delete the device object.IoDetachDevice(devExt->NextLowerDriver); RtlFreeUnicodeString (&devExt ->SymbolicLinkName); ExFreePool (devExt ->InputData); IoDeleteDevice(DeviceObject);

只有在对设备对象的所有引用都被释放后,I/O 管理器才会真正删除该设备对象。因此,在驱动程序的设备删除处理完成之后,有效的设备对象和设备扩展可能仍然存在。但是,驱动程序已经释放其资源,从而使得存储在设备扩展中的这些资源的指针都变得无效。

如果驱动程序在其删除设备处理完成之后,但是 I/O 管理器删除设备对象之前接收到另一个 I/O 请求,那么就会发生问题。当驱动程序处理请求时,它可能试图从设备扩展取消对一个无效指针的引用,这会导致系统崩溃。

要防止这种问题,驱动程序应该为所有类型的 I/O 请求获取删除锁,而不仅仅是即插即用和电源请求。大部分驱动程序已经在其 DispatchPnPDispatchPower 例程中获取了删除锁,从而防止在处理即插即用和电源 IRP 时删除设备。但是,因为其他类型的 IRP 可能在设备删除之后到达,所以驱动程序还应该在调度例程中为其他类型的 I/O 请求获得删除锁。

最简单的方法是在发送 IRP 时在 I/O 调度例程中调用 IoAcquireRemoveLockIoAcquireRemoveLock 返回 STATUS_DELETE_PENDING 来指示正在删除设备。如果 IoAcquireRemoveLock 返回此状态(或者除 STATUS_SUCCESS 之外的任何状态),那么驱动程序应该拒绝 I/O 请求。

您应该做什么?

在取消引用存储在设备扩展中的任何指针之前,通过在发送 IRP 时在 I/O 调度例程中调用 IoAcquireRemoveLock 来获得删除锁。

如果 IoAcquireRemoveLock 不返回 STATUS_SUCCESS,那么拒绝 I/O 请求。

当驱动程序完成 IRP 处理时,调用 IoReleaseRemoveLock

在设备删除处理期间调用 IoReleaseRemoveLockAndWait,然后调用 IoDetachDeviceIoDeleteDevice

更多信息:
Windows DDK
使用删除锁



此信息有用吗?