IO_REMOVE_LOCK(删除锁)的具体结构没有公开,WDK的文档中中查不到IO_REMOVE_LOCK。最开始看到IO_REMOVE_LOCK是在WDK的例子event中。下面是参考网上的一些资料之后的一点总结,错误的地方请指正。
为什么要用IO_REMOVE_LOCK?
WDM 驱动程序在处理设备删除 IRP 并释放驱动程序分配的内存后可能接收到附加的 IRP。在处理附加的 IRP 时试图引用已经释放的内存会导致系统崩溃。驱动程序能够接收已删除设备的 IRP,这有两个原因:
- 在设备被删除后,另一个组件可以发送 I/O。要发送一个 IRP,组件获取目标设备或文件的指针并去除该设备对象上的引用(或者 I/O 管理器代表组件去除引用)。引用可以确保目标设备或文件对象的持续性,从而目标驱动程序可以访问设备对象和设备扩展。但是,除非组件已经在目标设备上注册了即插即用通知,否则它不能确定设备是否仍然存在。
- 在设备删除请求之前发送的 I/O 请求可能在目标驱动程序处理设备删除请求之后到达。这种情况是否发生取决于哪个组件在发送 I/O、目标驱动程序在设备堆栈中的位置以及为设备挂起的其他 I/O 请求。
通俗一点的解释:有时候I/O管理器发出的PnP请求会与其它I/O请求(如包含读写的请求)同时出现。这完全有可能,例如当你处理其它IRP时收到了IRP_MN_REMOVE_DEVICE请求。你必须自己避免这种麻烦产生,标准的做法是使用一个IO_REMOVE_LOCK对象和几个相关的内核模式支持例程。
防止设备被过早地删除的基本想法是在每一次开始处理请求时都获取删除锁,处理完成后释放删除锁。在你删除你的设备对象前,应确保删除锁未被使用。否则,你将等到这个锁的所有引用都被释放。下图显示了这个过程:
怎么使用IO_REMOVE_LOCK?
在驱动程序的设备扩展(DEVICE_EXTENSION)中定义IO_REMOVE_LOCK类型的变量,并在 AddDevice例程中调用IoInitializeRemoveLock对其进行初始化。
此后,无论何时,当你收到一个I/O请求时(除了IRP_MJ_CREATE),你就调用IoAcquireRemoveLock。如果删除设备的操作正在进行,则IoAcquireRemoveLock返回STATUS_DELETE_PENDING。否则,该函数将获得删除锁并返回STATUS_SUCCESS。一旦你完成一个I/O操作,就调用IoReleaseRemoveLock,该函数将释放删除锁以及目前未处理的删除操作。
当处理一个设备删除请求 (IRP_MN_REMOVE_DEVICE) 时,驱动程序通过调用 IoReleaseRemoveLockAndWait 来释放在其 DispatchPnP 例程中获取的删除锁。这个调用直到与删除锁关联的引用计数达到零时才返回,这表示删除锁的所有其他持有者都已经被释放。在 IoReleaseRemoveLockAndWait 返回之后,驱动程序将 IRP 沿其设备堆栈向下传递(如有必要),调用 IoDetachDevice 来从设备堆栈中删除其设备对象,然后释放在其 AddDevice 例程中分配的资源(例如池内存)。最后,驱动程序调用 IoDeleteDevice 来标记要删除的设备对象。
何时调用IoReleaseRemoveLock?
驱动程序何时应该调用 IoReleaseRemoveLock 取决于它如何处理 IRP:通过将其传递给下一层驱动程序(不设置完成例程)、通过完成 IRP 而不将其传递给下一层驱动程序或者通过向下传递 IRP 并设置一个完成例程。
如果驱动程序将 IRP 传递给下一层驱动程序并且不设置 IoCompletion 例程,那么驱动程序应该在调用 IoCallDriver 之后调用 IoReleaseRemoveLock 来向下传递 IRP。
1
NTSTATUS MyDispatchRoutine (
2
IN PDEVICE_OBJECT DeviceObject,
3
IN PIRP Irp
4
)
5
{
6
PDEVICE_EXTENSION devExt;
7
NTSTATUS status;
8
9
devExt
=
(PDEVICE_EXTENSION) DeviceObject
->
DeviceExtension;
10
status
=
IoAcquireRemoveLock (
&
devExt
->
RemoveLock, Irp);
11
12
if
(
!
NT_SUCCESS (status)) {
//
maybe device is being removed.
13
Irp
->
IoStatus.Information
=
0
;
14
Irp
->
IoStatus.Status
=
status;
15
IoCompleteRequest (Irp, IO_NO_INCREMENT);
16
return
status;
17
}
18
19
//
Do request-specific processing
20
. . .
21
//
Pass down the IRP and release the lock.
22
IoSkipCurrentIrpStackLocation (Irp);
23
status
=
IoCallDriver (devExt
->
NextLowerDriver, Irp);
24
IoReleaseRemoveLock (
&
devExt
->
RemoveLock, Irp);
25
return
status;
26
}
如果驱动程序完成 IRP 并且不将其传递给下一层驱动程序,那么驱动程序应该在调用 IoCompleteRequest 之后调用 IoReleaseRemoveLock,如下例所示:
1
NTSTATUS MyDispatchRoutine (
2
IN PDEVICE_OBJECT DeviceObject,
3
IN PIRP Irp
4
)
5
{
6
PDEVICE_EXTENSION devExt;
7
NTSTATUS status;
8
9
devExt
=
(PDEVICE_EXTENSION) DeviceObject
->
DeviceExtension;
10
status
=
IoAcquireRemoveLock (
&
devExt
->
RemoveLock, Irp);
11
12
if
(
!
NT_SUCCESS (status)) {
//
maybe device is being removed.
13
Irp
->
IoStatus.Information
=
0
;
14
Irp
->
IoStatus.Status
=
status;
15
IoCompleteRequest (Irp, IO_NO_INCREMENT);
16
return
status;
17
}
18
19
//
Do request-specific processing
20
. . .
21
//
Request-specific processing is done. Complete the IRP
22
//
and release the lock.
23
24
Irp
->
IoStatus.Status
=
status;
25
IoCompleteRequest (Irp, IO_NO_INCREMENT );
26
IoReleaseRemoveLock (
&
devExt
->
RemoveLock, Irp);
27
return
status;
28
}
如果驱动程序将 IRP 传递给下一层驱动程序并设置一个 IoCompletion 例程,那么驱动程序将从 IoCompletion 例程调用 IoReleaseRemoveLock,如下所示:
1
NTSTATUS MyCompletionRoutine (
2
IN PDEVICE_OBJECT DeviceObject,
3
IN PIRP Irp,
4
IN PVOID Context
5
)
6
{
7
PDEVICE_EXTENSION data;
8
UNREFERENCED_PARAMETER (DeviceObject);
9
10
data
=
(PDEVICE_EXTENSION) Context;
11
IoReleaseRemoveLock (
&
data
->
RemoveLock, Irp);
12
return
STATUS_SUCCESS;
13
}
IO_REMOVE_LOCK小结
只有在对设备对象的所有引用都被释放后,I/O 管理器才会真正删除该设备对象。因此,在驱动程序的设备删除处理完成之后,有效的设备对象和设备扩展可能仍然存在。但是,驱动程序已经释放其资源,从而使得存储在设备扩展中的这些资源的指针都变得无效。
如果驱动程序在其删除设备处理完成之后,但是 I/O 管理器删除设备对象之前接收到另一个 I/O 请求,那么就会发生问题。当驱动程序处理请求时,它可能试图从设备扩展取消对一个无效指针的引用,这会导致系统崩溃。
要防止这种问题,驱动程序应该为所有类型的 I/O 请求获取删除锁,而不仅仅是即插即用和电源请求。大部分驱动程序已经在其 DispatchPnP 和 DispatchPower 例程中获取了删除锁,从而防止在处理即插即用和电源 IRP 时删除设备。但是,因为其他类型的 IRP 可能在设备删除之后到达,所以驱动程序还应该在调度例程中为其他类型的 I/O 请求获得删除锁。
最简单的方法是在发送 IRP 时在 I/O 调度例程中调用 IoAcquireRemoveLock。IoAcquireRemoveLock 返回 STATUS_DELETE_PENDING 来指示正在删除设备。如果 IoAcquireRemoveLock 返回此状态(或者除 STATUS_SUCCESS 之外的任何状态),那么驱动程序应该拒绝 I/O 请求。
- 在取消引用存储在设备扩展中的任何指针之前,通过在发送 IRP 时在 I/O 调度例程中调用 IoAcquireRemoveLock 来获得删除锁。
- 如果 IoAcquireRemoveLock 不返回 STATUS_SUCCESS,那么拒绝 I/O 请求。
- 当驱动程序完成 IRP 处理时,调用 IoReleaseRemoveLock。
- 在设备删除处理期间调用 IoReleaseRemoveLockAndWait,然后调用 IoDetachDevice 和 IoDeleteDevice。
Reference(其实基本都是转来的,稍微整理了一下)