Windows驅動之IO_REMOVE_LOCK

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;

這個成員主要有如下的作用:

  1. Removed : 鎖對象是否被移除。
  2. IoCount : 被請求的次數。
  3. 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,如下:

  1. Lock->Common.Removed = FALSE;初始化的時候,移除對象沒有移除,當前正常。
  2. Lock->Common.IoCount = 1; : 擁有的IO次數爲1.
  3. 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;
}
  1. InterlockedIncrement(&(Lock->Common.IoCount)); 遞增鎖的計數。
  2. 如果Lock->Common.Removed已經被移除了,那麼InterlockedDecrement(&(Lock->Common.IoCount))並釋放引用計數,然後返回STATUS_DELETE_PENDING
  3. 如果鎖沒有被釋放(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);
    }
}

這裏一個比較簡的邏輯:

  1. InterlockedDecrement(&(Lock->Common.IoCount))遞減IO引用計數。
  2. 如果計數遞減到0,那麼說明有人在等待這個鎖被刪除,設置事件。KeSetEvent(&(Lock->Common.RemoveEvent), IO_NO_INCREMENT, FALSE);

其中等待PIO_REMOVE_LOCK的流程爲IoReleaseRemoveLockAndWait.

5. IoReleaseRemoveLockAndWait

如果當設備要刪除的時候,我們調用:

  1. IoAcquireRemoveLock : 佔用移除鎖。
  2. 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);
    }

    //...
}

從上面流程我們知道:

  1. Lock->Common.Removed = TRUE; : 當IoReleaseRemoveLockAndWaitEx調用的時候,刪除鎖被釋放,也就是說其他地方已經不能在處理請求了,對象被刪除了。
  2. LockValue = InterlockedDecrement(&Lock->Common.IoCount); : 釋放自己IoAcquireRemoveLock佔用的引用。
  3. if (InterlockedDecrement(&Lock->Common.IoCount) > 0) : 這條語句比較重要,因爲IoReleaseRemoveLockAndWaitEx這個函數除了釋放IoAcquireRemoveLock佔用的引用計數之外,還需要釋放整個狀態(也就是說等待所有其他佔用鎖的完成), 在IoInitializeRemoveLock的時候IoCount初始化爲1,所以這裏要調用這個來判斷是不是還有其他線程在調用IoAcquireRemoveLock佔用鎖,如果是Lock->Common.IoCount大於0,那麼有其他線程請求了改鎖,需要等待完成。

6. 總結

其實整理來說IO_REMOVE_LOCK使用比較簡單,總結爲如下:

  1. IoInitializeRemoveLock : 初始化IO_REMOVE_LOCK
  2. 如果要進入禁止刪除操作的代碼段調用IoAcquireRemoveLock, 代碼調用完成之後,調用IoReleaseRemoveLock.
  3. 如果需要開始刪除關鍵數據,需要先IoReleaseRemoveLock獲取鎖,然後IoReleaseRemoveLockAndWaitEx釋放鎖,並等待其他線程代碼段執行完成。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章