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是什麼

怎麼使用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

NTSTATUS MyDispatchRoutine (
			IN PDEVICE_OBJECT DeviceObject,
			IN PIRP Irp
		)
{
	PDEVICE_EXTENSION   devExt;
	NTSTATUS    status;
 
	devExt = (PDEVICE_EXTENSION) DeviceObject->DeviceExtension;
	status = IoAcquireRemoveLock (&devExt->RemoveLock, Irp);
 
	if (!NT_SUCCESS (status)) { // maybe device is being removed.
		Irp->IoStatus.Information = 0;
		Irp->IoStatus.Status = status;
		IoCompleteRequest (Irp, IO_NO_INCREMENT);
		return status;
	}
 
	// Do request-specific processing
	. . . 
	// Pass down the IRP and release the lock.
	IoSkipCurrentIrpStackLocation (Irp);
	status = IoCallDriver (devExt->NextLowerDriver, Irp);
	IoReleaseRemoveLock (&devExt->RemoveLock, Irp);       
	return status;
}

如果驅動程序完成 IRP 並且不將其傳遞給下一層驅動程序,那麼驅動程序應該在調用 IoCompleteRequest 之後調用 IoReleaseRemoveLock,如下例所示:

NTSTATUS MyDispatchRoutine (
			IN PDEVICE_OBJECT DeviceObject,
			IN PIRP Irp
		)
{
	PDEVICE_EXTENSION   devExt;
	NTSTATUS    status;
 
	devExt = (PDEVICE_EXTENSION) DeviceObject->DeviceExtension;
	status = IoAcquireRemoveLock (&devExt->RemoveLock, Irp);
 
	if (!NT_SUCCESS (status)) { // maybe device is being removed.
		Irp->IoStatus.Information = 0;
		Irp->IoStatus.Status = status;
		IoCompleteRequest (Irp, IO_NO_INCREMENT);
		return status;
	}
 
	// Do request-specific processing
	. . . 
	// Request-specific processing is done. Complete the IRP
	// and release the lock.
 
	Irp->IoStatus.Status = status;
	IoCompleteRequest (Irp, IO_NO_INCREMENT );
	IoReleaseRemoveLock (&devExt->RemoveLock, Irp);       
	return status;
}

如果驅動程序將 IRP 傳遞給下一層驅動程序並設置一個 IoCompletion 例程,那麼驅動程序將從 IoCompletion 例程調用 IoReleaseRemoveLock,如下所示:

NTSTATUS MyCompletionRoutine (
			IN PDEVICE_OBJECT DeviceObject,
			IN PIRP Irp,
			IN PVOID Context
		)
{
	PDEVICE_EXTENSION   data;
	UNREFERENCED_PARAMETER (DeviceObject);
 
	data = (PDEVICE_EXTENSION) Context;
	IoReleaseRemoveLock (&data->RemoveLock, Irp);
	return STATUS_SUCCESS;
}

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 請求。

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

Reference(其實基本都是轉來的,稍微整理了一下)
我的設備不見了。爲什麼我仍然收到 IRP?
《Programming the Microsoft Windows Driver Model》

本文首發於:程序人生 >> IO_REMOVE_LOCK使用方法小結 &http://blog.csdn.net/msbsod

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章