文章目錄
Windows驅動之IO_REMOVE_LOCK
在設備驅動中,我們很多情況下會遇到這樣的問題,各種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
);
下面我們來看下這些函數的操作過程.
1. IO_REMOVE_LOCK
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
: 用來通知所有對象都被移除。
2. IoInitializeRemoveLock
在使用之前,必須先初始化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
: 初始化移除通知事件。
3. IoAcquireRemoveLock
當我們在操作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),那麼直接請求佔用鎖。
4. IoReleaseRemoveLockEx
佔用刪除鎖之後,使用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引用計數。- 如果計數遞減到0,那麼說明有人在等待這個鎖被刪除,設置事件。
KeSetEvent(&(Lock->Common.RemoveEvent), IO_NO_INCREMENT, FALSE);
其中等待PIO_REMOVE_LOCK
的流程爲IoReleaseRemoveLockAndWait
.
5. 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,那麼有其他線程請求了改鎖,需要等待完成。
6. 總結
其實整理來說IO_REMOVE_LOCK
使用比較簡單,總結爲如下:
IoInitializeRemoveLock
: 初始化IO_REMOVE_LOCK
。- 如果要進入禁止刪除操作的代碼段調用
IoAcquireRemoveLock
, 代碼調用完成之後,調用IoReleaseRemoveLock
. - 如果需要開始刪除關鍵數據,需要先
IoReleaseRemoveLock
獲取鎖,然後IoReleaseRemoveLockAndWaitEx
釋放鎖,並等待其他線程代碼段執行完成。