在设备驱动中,我们很多情况下会遇到这样的问题,各种PNP请求不是同步请求过来的;例如当我们在Read、Write设备的时候,可能设备这个时候接受到了IRP_MN_REMOVE_DEVICE
移除设备的请求,如果这个时候删除资源,那么其他IRP就可能出错了。为了避免这种问题的出现,Windows引入了IO_REMOVE_LOCK
移除锁。
IO_REMOVE_LOCK
的操作函数如下:
VOID IoInitializeRemoveLock(
_In_ PIO_REMOVE_LOCK Lock,
_In_ ULONG AllocateTag,
_In_ ULONG MaxLockedMinutes,
_In_ ULONG HighWatermark
);
NTSTATUS IoAcquireRemoveLock(
_In_ PIO_REMOVE_LOCK RemoveLock,
_In_opt_ PVOID Tag
);
VOID IoReleaseRemoveLock(
_In_ PIO_REMOVE_LOCK RemoveLock,
_In_ PVOID Tag
);
VOID IoReleaseRemoveLockAndWait(
_In_ PIO_REMOVE_LOCK RemoveLock,
_In_ PVOID Tag
);
下面我们来看下这些函数的操作过程.
typedef struct _IO_REMOVE_LOCK_COMMON_BLOCK {
BOOLEAN Removed;
BOOLEAN Reserved [3];
LONG IoCount;
KEVENT RemoveEvent;
} IO_REMOVE_LOCK_COMMON_BLOCK;
typedef struct _IO_REMOVE_LOCK {
IO_REMOVE_LOCK_COMMON_BLOCK Common;
#if DBG
IO_REMOVE_LOCK_DBG_BLOCK Dbg;
#endif
} IO_REMOVE_LOCK, *PIO_REMOVE_LOCK;
这个成员主要有如下的作用:
Removed
: 锁对象是否被移除。IoCount
: 被请求的次数。RemoveEvent
: 用来通知所有对象都被移除。在使用之前,必须先初始化IO_REMOVE_LOCK
,如下:
NTSTATUS AddDevice(PDRIVER_OBJECT DriverObject, PDEVICE_OBJECT pdo)
{
//...
IoInitializeRemoveLock(&pdx->RemoveLock, 0, 0, 0);
//...
}
其中IoInitializeRemoveLock
的实现如下:
VOID
NTAPI
IoInitializeRemoveLockEx(IN PIO_REMOVE_LOCK RemoveLock,
IN ULONG AllocateTag,
IN ULONG MaxLockedMinutes,
IN ULONG HighWatermark,
IN ULONG RemlockSize)
{
PEXTENDED_IO_REMOVE_LOCK Lock = (PEXTENDED_IO_REMOVE_LOCK)RemoveLock;
{
return;
}
switch (RemlockSize)
{
case (sizeof(IO_REMOVE_LOCK_DBG_BLOCK) + sizeof(IO_REMOVE_LOCK_COMMON_BLOCK)):
//调试信息
case sizeof(IO_REMOVE_LOCK_COMMON_BLOCK):
Lock->Common.Removed = FALSE;
Lock->Common.IoCount = 1;
KeInitializeEvent(&Lock->Common.RemoveEvent,
SynchronizationEvent,
FALSE);
}
}
这个函数的主要作用是初始化PIO_REMOVE_LOCK
,如下:
Lock->Common.Removed = FALSE;
初始化的时候,移除对象没有移除,当前正常。Lock->Common.IoCount = 1;
: 拥有的IO次数为1.KeInitializeEvent(&Lock->Common.RemoveEvent
: 初始化移除通知事件。当我们在操作IRP的时候,可以先请求移除锁,例如如下:
NTSTATUS DispatchSomething(PDEVICE_OBJECT fdo, PIRP Irp)
{
PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION) fdo->DeviceExtension;
NTSTATUS status = IoAcquireRemoveLock(&pdx->RemoveLock, Irp);
if (!NT_SUCCESS(status))
return CompleteRequest(Irp, status, 0);
//...
IoReleaseRemoveLock(&pdx->RemoveLock, Irp);
return CompleteRequest(Irp, <some code>, <info value>);
}
在请求IPR的时候,先调用IoAcquireRemoveLock
占用删除锁,防止设备被删除,IRP完成之后,再调用IoReleaseRemoveLock
释放删除锁。
需要注意的是IoAcquireRemoveLock
当锁被删除之后,返回STATUS_DELETE_PENDING
, 函数实现如下:
NTSTATUS
NTAPI
IoAcquireRemoveLockEx(IN PIO_REMOVE_LOCK RemoveLock,
IN OPTIONAL PVOID Tag,
IN LPCSTR File,
IN ULONG Line,
IN ULONG RemlockSize)
{
KIRQL OldIrql;
LONG LockValue;
PIO_REMOVE_LOCK_TRACKING_BLOCK TrackingBlock;
PEXTENDED_IO_REMOVE_LOCK Lock = (PEXTENDED_IO_REMOVE_LOCK)RemoveLock;
LockValue = InterlockedIncrement(&(Lock->Common.IoCount));
ASSERT(LockValue > 0);
if (!Lock->Common.Removed)
{
//...
}
else
{
if (!InterlockedDecrement(&(Lock->Common.IoCount)))
{
KeSetEvent(&(Lock->Common.RemoveEvent), IO_NO_INCREMENT, FALSE);
}
return STATUS_DELETE_PENDING;
}
return STATUS_SUCCESS;
}
InterlockedIncrement(&(Lock->Common.IoCount));
递增锁的计数。Lock->Common.Removed
已经被移除了,那么InterlockedDecrement(&(Lock->Common.IoCount))
并释放引用计数,然后返回STATUS_DELETE_PENDING
。Lock->Common.Removed
为FALSE),那么直接请求占用锁。占用删除锁之后,使用IoReleaseRemoveLockEx
来释放删除锁,这个代码如下:
VOID
NTAPI
IoReleaseRemoveLockEx(IN PIO_REMOVE_LOCK RemoveLock,
IN PVOID Tag,
IN ULONG RemlockSize)
{
LONG LockValue;
//...
LockValue = InterlockedDecrement(&(Lock->Common.IoCount));
if (!LockValue)
{
KeSetEvent(&(Lock->Common.RemoveEvent), IO_NO_INCREMENT, FALSE);
}
}
这里一个比较简的逻辑:
InterlockedDecrement(&(Lock->Common.IoCount))
递减IO引用计数。KeSetEvent(&(Lock->Common.RemoveEvent), IO_NO_INCREMENT, FALSE);
其中等待PIO_REMOVE_LOCK
的流程为IoReleaseRemoveLockAndWait
.
如果当设备要删除的时候,我们调用:
IoAcquireRemoveLock
: 占用移除锁。IoReleaseRemoveLockAndWait
: 释放锁,并等待所有的删除锁占用被释放。NTSTATUS DispatchPnp(PDEVICE_OBJECT fdo, PIRP Irp)
{
PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION) fdo->DeviceExtension;
NTSTATUS status = IoAcquireRemoveLock(&pdx->RemoveLock, Irp);
if (!NT_SUCCESS(status))
return CompleteRequest(Irp, status, 0);
//...
status = (*fcntab[fcn](fdo, Irp);
if (fcn != IRP_MN_REMOVE_DEVICE)
IoReleaseRemoveLock(&pdx->RemoveLock, Irp);
return
}
NTSTATUS HandleRemoveDevice(PDEVICE_OBJECT fdo, PIRP Irp)
{
Irp->IoStatus.Status = STATUS_SUCCESS;
PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION) fdo->DeviceExtension;
AbortRequests(&pdx->dqReadWrite, STATUS_DELETE_PENDING);
DeregisterAllInterfaces(pdx);
StopDevice(fdo, pdx->state == WORKING);
pdx->state = REMOVED;
NTSTATUS status = DefaultPnpHandler(pdx->LowerDeviceObject, Irp);
IoReleaseRemoveLockAndWait(&pdx->RemoveLock, Irp);
RemoveDevice(fdo);
return status;
}
其中IoReleaseRemoveLockAndWait
这个函数的代码如下:
VOID
NTAPI
IoReleaseRemoveLockAndWaitEx(IN PIO_REMOVE_LOCK RemoveLock,
IN PVOID Tag,
IN ULONG RemlockSize)
{
LONG LockValue;
PIO_REMOVE_LOCK_TRACKING_BLOCK TrackingBlock;
PEXTENDED_IO_REMOVE_LOCK Lock = (PEXTENDED_IO_REMOVE_LOCK)RemoveLock;
PAGED_CODE();
Lock->Common.Removed = TRUE;
LockValue = InterlockedDecrement(&Lock->Common.IoCount);
if (InterlockedDecrement(&Lock->Common.IoCount) > 0)
{
KeWaitForSingleObject(&(Lock->Common.RemoveEvent),
Executive,
KernelMode,
FALSE,
NULL);
}
//...
}
从上面流程我们知道:
Lock->Common.Removed = TRUE;
: 当IoReleaseRemoveLockAndWaitEx
调用的时候,删除锁被释放,也就是说其他地方已经不能在处理请求了,对象被删除了。LockValue = InterlockedDecrement(&Lock->Common.IoCount);
: 释放自己IoAcquireRemoveLock
占用的引用。if (InterlockedDecrement(&Lock->Common.IoCount) > 0)
: 这条语句比较重要,因为IoReleaseRemoveLockAndWaitEx
这个函数除了释放IoAcquireRemoveLock
占用的引用计数之外,还需要释放整个状态(也就是说等待所有其他占用锁的完成), 在IoInitializeRemoveLock
的时候IoCount
初始化为1,所以这里要调用这个来判断是不是还有其他线程在调用IoAcquireRemoveLock
占用锁,如果是Lock->Common.IoCount
大于0,那么有其他线程请求了改锁,需要等待完成。其实整理来说IO_REMOVE_LOCK
使用比较简单,总结为如下:
IoInitializeRemoveLock
: 初始化IO_REMOVE_LOCK
。IoAcquireRemoveLock
, 代码调用完成之后,调用IoReleaseRemoveLock
.IoReleaseRemoveLock
获取锁,然后IoReleaseRemoveLockAndWaitEx
释放锁,并等待其他线程代码段执行完成。