磁盤的過濾
10.3 驅動分析
10.3.3 PnP 請求的處理
作爲一個捲過濾驅動 PnP 請求處理是非常重要的,因爲 Windows 操作系統在某些時刻會向存儲設備發出專門的請求,如果沒有進行正確的處理,將會造成系統無法正常關機等一系列問題。
在收到 PnP 請求之後,由於在 DriverEntry 中對 PnP 請求的處理函數特別設置成了 DPDispatchPnp 函數,所以這個函數將會被調用。它有兩個參數: DeviceObject 和 irp,分別說明了這個請求發往的設備和這個請求的具體細節。由於這是過濾驅動的 PnP 分發函數,所以只有過濾驅動所建立的設備收到 PnP 請求時纔會調用這個函數,那麼我們應該很容易的想到,這個 DeviceObject 是過濾設備的設備對象。
//用來指向過濾設備的設備擴展的指針
PDP_FILTER_DEV_EXTENSION DevExt = DeviceObject->DeviceExtension;
//返回值
NTSTATUS ntStatus = STATUS_SUCCESS;
//用來指向 irp stack 的指針
PIO_STACK_LOCATION irpsp = IoGetCurrentIrpStackLocation(Irp);
在獲取到了這些參數之後,可以通過判斷 irp stack 中的 MinorFunction 來判斷這個 IRP 請求的具體目的是什麼。在 irp stack 中,通常會存在 MajorFunction 和 MinorFunction 兩個請求號。前者爲大請求號,一般是類似於 Write、Read、PnP、DeviceIoControl 等大分類的請求;而後者是小請求號,一般是在某一個大類中的子請求號。在這裏,我們已知大請求號一定爲 PnP,那麼我們只需要關心小請求號。
第一個需要處理的 PnP 子請求號是設備移除請求,這個請求會在 Windows 進行設備熱插拔、均衡、或者關機的時候被髮送到磁盤卷設備。當然,過濾驅動設備會先於磁盤卷設備收到這個請求,在這個請求發送時,所有的磁盤卷設備的讀寫請求都應該已經完成,所以在過濾驅動收到這個請求的時候,只需要間的地將曾經建立過的所有設備和初始化過的所有內部數據結構全部銷燬即可。建立過的設備主要是在 AddDevice 函數中建立的過濾設備和綁定而生成的下層設備,內部數據結構主要包括了下面將要介紹到的 bitmap 數據結構。此外,在 AddDevice 函數中爲卷設備建立的請求處理線程也需要被停止。
PIO_STACK_LOCATION irpsp = IoGetCurrentIrpStackLocation(Irp);
switch(irpsp->MinorFunction)
{
case IRP_MN_REMOVE_DEVICE:
//如果是PnP manager發過來的移除設備的irp,將進入這裏
{
//這裏主要做一些清理工作
if (DevExt->ThreadTermFlag != TRUE && NULL != DevExt->ThreadHandle)
{
//如果線程還在運行的話需要停止它,這裏通過設置線程停止運行的標誌並且發送事件信息,讓線程自己終止運行
DevExt->ThreadTermFlag = TRUE;
KeSetEvent(
&DevExt->ReqEvent,
(KPRIORITY) 0,
FALSE
);
//等待線程結束
KeWaitForSingleObject(
DevExt->ThreadHandle,
Executive,
KernelMode,
FALSE,
NULL
);
//解除引用線程對象
ObDereferenceObject(DevExt->ThreadHandle);
}
if (NULL != DevExt->Bitmap)
{
//如果還有位圖,就釋放
DPBitmapFree(DevExt->Bitmap);
}
if (NULL != DevExt->LowerDevObj)
{
//如果存在着下層設備,就先去掉掛接
IoDetachDevice(DevExt->LowerDevObj);
}
if (NULL != DevExt->FltDevObj)
{
//如果存在過濾設備,就要刪除它
IoDeleteDevice(DevExt->FltDevObj);
}
break;
}
第二個需要處理的請求是設備使用通告請求, Windows 操作系統會在建立或者刪除特殊文件的時候向存儲設備發出這個 IRP 請求,作爲存儲設備捲過濾設備自然也會收到這個請求。這裏說的特殊文件通常包括頁面文件、休眠文件和 dump 文件。Windows 會通過 irp stack 中的 ParametersUsageNotification.Type 域來說明請求的是哪種文件,並且會使用 ParametersUsageNotification.InPath 域來說明這個請求是在詢問是否可以建立這個文件,還是刪除了這個文件之後對這個設備的通知。在處理這個請求的時候,過濾驅動比較關心的是對頁面文件的處理,因爲這牽扯到過濾設備標誌位中的 DO_POWER_PAGABLE 位。關於這個位,簡單的說就是如果有頁面文件在這個捲上,那麼就應該清除該標誌位。否則就加上這個位。
這個請求的根本目的是, Windows 系統用來查詢是否可以在其上建立特殊文件,作爲過濾驅動不該對這種請求做出回答的。正確的做法就是將該請求發送給下層設備。同時要監視下層設備的回答,如果下層設備不支持這個請求,過濾設備就什麼都可以不做。如果下層設備支持這個請求,那麼過濾設備就要做相應的處理。比如在下層設備對一個頁面文件的建立請求回答是之後,過濾設備就要對 DO_POWER_PAGABLE 位進行相應的設置,並做一個計數,這個計數會隨着頁面文件建立的請求增加而增加,隨摺頁面文件刪除通知而減少,當減少到最後一個計數時,過濾設備又需要對 DO_POWER_PAGABLE 進行相應的設置。
//這個是PnP 管理器用來詢問設備能否支持特殊文件的irp,作爲卷的過濾驅動,我們必須處理
case IRP_MN_DEVICE_USAGE_NOTIFICATION:
{
BOOLEAN setPagable;
//如果是詢問是否支持休眠文件和dump文件,則直接下發給下層設備去處理
if (irpsp->Parameters.UsageNotification.Type != DeviceUsageTypePaging)
{
ntStatus = DPSendToNextDriver(
DevExt->LowerDevObj,
Irp);
return ntStatus;
}
//這裏等一下分頁計數事件
ntStatus = KeWaitForSingleObject(
&DevExt->PagingPathCountEvent,
Executive,
KernelMode,
FALSE,
NULL);
//setPagable初始化爲假,是沒有設置過DO_POWER_PAGABLE的意思
setPagable = FALSE;
if (!irpsp->Parameters.UsageNotification.InPath &&
DevExt->PagingPathCount == 1 )
{
//如果是PnP manager通知我們將要刪去分頁文件,且我們目前只剩下最後一個分頁文件的時候會進入這裏
if (DeviceObject->Flags & DO_POWER_INRUSH)
{}
else
{
//到這裏說明沒有分頁文件在這個設備上了,需要設置DO_POWER_PAGABLE這一位了
DeviceObject->Flags |= DO_POWER_PAGABLE;
setPagable = TRUE;
}
}
//到這裏肯定是關於分頁文件的是否可建立查詢,或者是刪除的通知,我們交給下層設備去做。這裏需要用同步的方式給下層設備,也就是說要等待下層設備的返回
ntStatus = DPForwardIrpSync(DevExt->LowerDevObj,Irp);
if (NT_SUCCESS(ntStatus))
{
//如果發給下層設備的請求成功了,說明下層設備支持這個操作,會執行到這裏
//在成功的條件下我們來改變我們自己的計數值,這樣就能記錄我們現在這個設備上到底有多少個分頁文件
IoAdjustPagingPathCount(
&DevExt->PagingPathCount,
irpsp->Parameters.UsageNotification.InPath);
if (irpsp->Parameters.UsageNotification.InPath)
{
if (DevExt->PagingPathCount == 1)
{
//如果這個請求是一個建立分頁文件的查詢請求,並且下層設備支持這個請求,而且這是第一個在這個設備上的分頁文件,那麼我們需要清除DO_POWER_PAGABLE位
DeviceObject->Flags &= ~DO_POWER_PAGABLE;
}
}
}
else
{
//到這裏說明給下層設備發請求失敗了,下層設備不支持這個請求,這時候我們需要把之前做過的操作還原
if (setPagable == TRUE)
{
//根據setPagable變量的值來判斷我們之前是否做過對DO_POWER_PAGABLE的設置,如果有的話就清楚這個設置
DeviceObject->Flags &= ~DO_POWER_PAGABLE;
setPagable = FALSE;
}
}
//設置分頁計數事件
KeSetEvent(
&DevExt->PagingPathCountEvent,
IO_NO_INCREMENT,
FALSE
);
//到這裏我們就可以完成這個irp請求了
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return ntStatus;
}
這個請求基本屬於每個磁盤必須的操作,沒有什麼自定義的空間。在需要的時候只需要查文檔填入即可。相信在未來也會被框架化。在這裏,對於其他的 PnP 請求,直接交給下層設備處理即可。
10.3.4 Power 請求的處理
本書在這裏考慮了 Windows Vista 和 Windows XP 之間版本區別,對電源做了不同的處理。而電源請求也是向下層下發即可,在本書這裏的學習意義不大。
明日計劃
大概在15號交初稿以前,都以論文爲重心。減少其他的學習時間,但是作爲終身學習習慣的培養,還是要堅持下去。所以明日仍然爲:
搞論文
驅動編程- bitmap 的作用和分析