天天看點

IO_REMOVE_LOCK(删除鎖)

IO_REMOVE_LOCK(删除鎖)的具體結構沒有公開,WDK的文檔中中查不到IO_REMOVE_LOCK。最開始看到IO_REMOVE_LOCK是在WDK的例子event中。下面是參考網上的一些資料之後的一點總結,錯誤的地方請指正。

為什麼要用IO_REMOVE_LOCK?

WDM 驅動程式在處理裝置删除 IRP 并釋放驅動程式配置設定的記憶體後可能接收到附加的 IRP。在處理附加的 IRP 時試圖引用已經釋放的記憶體會導緻系統崩潰。驅動程式能夠接收已删除裝置的 IRP,這有兩個原因:

  1. 在​​裝置​​被删除後,另一個元件可以發送 I/O。要發送一個 IRP,元件擷取目标裝置或檔案的指針并去除該裝置對象上的引用(或者 I/O 管理器代表元件去除引用)。引用可以確定目标裝置或檔案對象的持續性,進而目标驅動程式可以通路裝置對象和裝置擴充。但是,除非元件已經在目标裝置上注冊了即插即用通知,否則它不能确定裝置是否仍然存在。
  2. 在裝置删除請求之前發送的 I/O 請求可能在目标驅動程式處理裝置删除請求之後到達。這種情況是否發生取決于哪個元件在發送 I/O、目标驅動程式在裝置堆棧中的位置以及為裝置挂起的其他 I/O 請求。

通俗一點的解釋:有時候I/O管理器發出的PnP請求會與其它I/O請求(如包含讀寫的請求)同時出現。這完全有可能,例如當你處理其它IRP時收到了IRP_MN_REMOVE_DEVICE請求。你必須自己避免這種麻煩産生,标準的做法是使用一個​​IO_REMOVE_LOCK​​對象和幾個相關的核心模式支援例程。

防止裝置被過早地删除的基本想法是在每一次開始處理請求時都擷取删除鎖,處理完成後釋放​​删除鎖​​。在你删除你的裝置對象前,應確定删除鎖未被使用。否則,你将等到這個鎖的所有引用都被釋放。下圖顯示了這個過程:

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。

[部落客注:Remove_Lock的demo可參考ddk sample中的ioctl]

IO_REMOVE_LOCK小結

隻有在對裝置對象的所有引用都被釋放後,I/O 管理器才會真正删除該裝置對象。是以,在驅動程式的裝置删除處理完成之後,有效的裝置對象和裝置擴充可能仍然存在[注1]。但是,驅動程式已經釋放其資源,進而使得存儲在裝置擴充中的這些資源的指針都變得無效。

如果驅動程式在其删除裝置處理完成之後,但是 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 請求。

  1. 在取消引用存儲在裝置擴充中的任何指針之前,通過在發送 IRP 時在 I/O 排程例程中調用 IoAcquireRemoveLock 來獲得删除鎖。
  2. 如果 IoAcquireRemoveLock 不傳回 STATUS_SUCCESS,那麼拒絕 I/O 請求。
  3. 當驅動程式完成 IRP 處理時,調用 IoReleaseRemoveLock。
  4. 在裝置删除處理期間調用 IoReleaseRemoveLockAndWait,然後調用 IoDetachDevice 和 IoDeleteDevice。

部落客[注1]:

NTAPI
IoDeleteDevice(IN PDEVICE_OBJECT DeviceObject)
{
    PIO_TIMER Timer;

    /* Check if the device is registered for shutdown notifications */
    if (DeviceObject->Flags & DO_SHUTDOWN_REGISTERED)
    {
        /* Call the shutdown notifications */
        IoUnregisterShutdownNotification(DeviceObject);
    }

    /* Check if it has a timer */
    Timer = DeviceObject->Timer;
    if (Timer)
    {
        /* Remove it and free it */
        IopRemoveTimerFromTimerList(Timer);
        ExFreePoolWithTag(Timer, TAG_IO_TIMER);
    }

    /* Check if the device has a name */
    if (DeviceObject->Flags & DO_DEVICE_HAS_NAME)
    {
        /* It does, make it temporary so we can remove it */
        ObMakeTemporaryObject(DeviceObject);
    }

    /* Set the pending delete flag */
    IoGetDevObjExtension(DeviceObject)->ExtensionFlags |= DOE_DELETE_PENDING;

    /* Check if the device object can be unloaded */
    if (!DeviceObject->ReferenceCount) IopUnloadDevice(DeviceObject);
}      
NTSTATUS
FilterPass (
    PDEVICE_OBJECT DeviceObject,
    PIRP Irp
    )
{
    PDEVICE_EXTENSION           deviceExtension;
    NTSTATUS    status;
    
    deviceExtension = (PDEVICE_EXTENSION) DeviceObject->DeviceExtension;
    status = IoAcquireRemoveLock (&deviceExtension->RemoveLock, Irp);
    if (!NT_SUCCESS (status)) {
        Irp->IoStatus.Status = status;
        IoCompleteRequest (Irp, IO_NO_INCREMENT);
        return status;
    }      

繼續閱讀