驅動開發學習筆記1

时间:2021-11-05 15:13:42

1:設備對象是系統為幫組軟件管理硬件而創建的數據結構,一個物理硬件可以有多個這樣的數據結構。處於堆棧最底層的設備對象稱為物理設備對象(PDO);

2:操作系統的pnp管理器按照設備驅動程序的要求構造了設備對象堆棧。總線驅動程序的任務就是枚舉總線上的設備。并為每個設備創建一個PDO;一旦總線驅動程序檢查到新硬件存在。Pnp管理器就創建一個PDO.創建完PDO后。PNP管理器參照注冊表中的信息查找于這個PDO相關的過濾器和功能驅動程序。系統安裝程序負責添加這些注冊表項。而驅動程序包中的控制硬件安裝的INF文件負責添加其他表項。這些表項定義了過濾器和功能驅動程序在堆棧中的次序。

PNP管理器先裝入最底層的過濾器驅動程序并調用其AddDevice函數。這個函數創建一個FIDO。這樣在過濾器驅動程序和FIDO之間建立了水平連接。然後ADDDevice把PDO連接到FIDO上。這就是設備對象之間連線的由來。PNP管理器繼續向上執行。裝入并調用每個底層過濾器,功能驅動程序,每個高層過濾器,直到完成整個堆棧。

3:通常IRP先被送到設備堆棧的最上層驅動程序。然後逐步的過濾到下面的驅動程序。每一層驅動程序都可以決定如何處理IRP,有時候驅動程序不做任何事情。只是向下層穿IRP。有時。驅動程序直接處理完該IRP不再向下傳遞。還有時。驅動程序處理了IRP又把IRP傳遞下去。這取決于設備以及IRP所攜帶的內容。

4:系統這么裝入驅動程序?

既然總線驅動程序創建了PDO。然後PDO管理器根據改PDO的注冊表項裝入它的驅動程序。那么總線驅動程序從哪裡來???

首先,PNP管理器有一個內建的驅動程序。它與一個實際不存在的根總線相對應。根總線概念性的把計算機與所有那些不能用電子方式申明自己存在的設備連接起來。這包括主硬件總線(如PCI)。根總線驅動程序從注冊表中獲取有關計算機的信息。而這些關於計算機本身的注冊表信息是由windows系統安裝程序初始化的。安裝程序通過運行一個盡心製作的硬件檢測程序以及向用戶提出一些適當的問題來獲取這些信息。所以,根總線驅動程序有足夠的信息為主總線創建PDO.然後,主總線的功能驅動程序用電子方式枚舉自己的硬件。

5:有三種注冊表鍵負責配置。它們是硬件鍵。類鍵。服務建。硬件鍵包括單個設備的信息。類鍵涉及到所有相同類型設備的共同信息。服務鍵包含驅動程序信息。一般注冊表中都會包含一些以前用過的和現在用過的硬件信息。

設備的硬件鍵出現在注冊表local machine分支的\system\currentControlSet\Enum子鍵上。Enum下的第一級子鍵與系統中的各種總線枚舉器相對應。SetupDixXXX函數可以訪問Enum鍵。

假設你的驅動程序使用IoRegisterDeviceInterface函數注冊了一個設備接口。並且你有一個改接口的符號連接名(通過枚舉該接口GUID的所有實例或從WM_DeviceChange消息的參數中獲得這個名字)。

類鍵,所有設備類的類鍵都出現在HKLM\system\currentcontrolset\control\class鍵中。每個設備下還可以在所屬類鍵下擁有自己的子鍵。該鍵的鍵名就是設備硬件鍵中的Driver值。這個子鍵的作用是把所有這些注冊表項與安裝設備使用的INF文件關聯。

服務建:HKLM\System\CurrentControlSet\Services鍵中。

ImagePath:指出驅動程序執行文件的名字和路徑。

6:當說系統裝入一個驅動程序時。是指系統把驅動程序的映像映射到虛擬內存中。并重定位內存參考。最後調用驅動程序的主入口點。如果驅動程序已經在內存中。則裝入過程僅僅是增加驅動程序映像的參考計數。

7:I/O管理器適用驅動程序對象來代表每個設備驅動程序。

WDM驅動程序可以調用IOCreateDevice函數創建設備對象。但設備對象的管理則由I/O管理器負責。

DriverObject指向與該設備對象相關的驅動程序對象。通常就是調用IOCreateDevice函數創建該設備對象的驅動程序對象。NextDevice指向屬於同一個驅動程序的下一個設備對象。CurrentIrp指向最近發往驅動程序StartIO函數的I/O請求包。

Device_Object結構的Flags標誌:

這只列舉主要的幾個

DO_BUFFERD_IO:讀寫操作適用緩沖方式(系統複製緩衝區)。

DO_DIRECT_IO:讀寫操作適用直接方式(內存描述符表)

8:如何建立設備堆棧???

NextDevice域把所有屬於特定驅動程序的設備對象水平的連接在一起。而垂直方向上的鏈接主要用不透明域。從PDO開始。每個設備對象都有指向上一層設備對象的指針。由於沒有已公開的向下方向的指針。所以驅動程序必須自己記住下層設備對象是誰。

每個wdm驅動程序必須能處理PNP,Power,System_control這三種請求。

DriverObject->MajorFunction[IRP_MJ_PNP]=DispatchPnp;

DriverObject->MajorFunction[IRP_MJ_POWER]=DispatchPower;

DrvierObject->MajorFunction[IRP_MJ_SYSTEM_CONTROL] = DispatchWmi;

9:非WDM驅動程序需要在DriverEntry例程中枚舉他們的硬件,并在其硬件的所有可能實例被識別前裝入內存并初始化。例如。鼠標和鍵盤就是這樣的。但如果DriverEntry例程運行的太快。那么這些驅動程序將不能正常工作。因此,它們必須使用IORegisterDriverReinitialization函數寄存一個例程。之後I/O管理器在某個驅動程序檢測到新硬件存在時再回調這個寄存例程。最後在初始化例程運行。同時也把自身寄存為下一個回調的函數。

WDM驅動程序不需要寄存再初始化例程。因為它們不需要用自己的代碼去檢測硬件。Pnp管理器自動把新硬件匹配到正確的WDM驅動程序上。并調用改驅動的ADDDevice例程。再由AddDevice例程做所有必要的初始化工作。

對於功能驅動程序。其AddDevice函數的基本職責是創建一個設備對象并把它連接到以pdo為底的設備堆棧中。相關步驟

(1:調用IOCreateDevice創建設備對象,并建立一個私有的設備擴展對象。

(2:注冊一個多多個設備接口,以便應用程序能知道設備的存在。另外。還可以給出設備名并創建符號連接。

(3:初始化設備擴展和設備對象的flag成員。

(4:調用IoAttachDeviceToDeviceStack函數把新設備對象放到堆棧上。

10:用戶模式程序可以用DefineDosDevice創建一個符號連接。如下:

BOOL  okay = DefineDosDevice(DDD_RAW_TARGET_PATH,”barf”,”\\Device\\SECTEST_0”);

在WDM中IOCreateSymbolicLink(Linkname,targname);

11:應該命名設備對象嗎???

如果命名了設備對象。那么任何內核模式程序都可以打開改設備的句柄。另外。任何內核模式或用戶模式都能創建連接到該設備的符號連接。并可以使用這個符號連接打開設備的句柄。

12:建立設備堆

每個過濾器驅動程序和功能驅動程序都有責任把設備對象放到設備堆棧上。從PDO開始一直向上。

PDEVICE_OBJECT fdo;

IoCreateDevice(…,&fdo);

Pdx->LowerDeviceObject = IoAttachDeviceToDeviceStack(fdo,pdo);

13:清除DO_DEVICE_INITIALIZING標誌

在AddDevice中最後一件要做的事情是清除設備對象中的DO_Device_initiatizing標誌、

Fdo->Flags&=~DO_DEVICE_INITIALIZING;

當這個標誌設置時。I/O管理器將拒絕任何打開改設備句柄的請求或向設備對象上附著其他設備對象的請求。在驅動程序完成初始化后。必須清除這個標誌。

1:創建完IRP后。你可以調用IOGetNextIrpStackLocation函數獲得該IRP第一個堆棧單元的指針。然後初始化這個堆棧單元。在初始化過程的最後,你需要填充MajorFunction代碼。堆棧單元初始化完成后,就可以調用IoCallDriver函數把irp發送到設備驅動程序。

PDEVICE_OBJECT DeviceObject;

PIO_STACK_LOCATION stack = IOGetNextIrpStackLoction(Irp);

Stack->MajorFunction = IRP_MJ_Xxx;

NTSTATUS status = IoCallDriver(DeviceObject,irp);

2:當有大量讀寫請求進入設備時,通常需要把這些請求放入一個隊列中。以便使硬件訪問串行化。每個設備對象都有一個請求隊列對象。下面是使用這個隊列的標準方法。

NTSTATUS DispatchXxx(…)

{

    ….

    IoMarkIrpPending(IRP);

    IoStartPacket(device,irp,NULL,NULL);

    Return STATUS_PENDING;

}

(1:無論何時。如果當派遣函數返回STATUS_PENDING狀態代碼的時候,你應該先調用這個IoMarkIrpPending函數。以幫組IO管理器避免內部競爭,我們必須在放棄IRP所有權之前做這一點。

如果設備正忙。那么IoStartPacket就把請求放到隊列中。如果設備空閒,IoStartPacket將把設備置成忙并調用StartIO例程。

返回Stauts_pending通知調用者我們沒有完成這個IRP;

注意一旦我們調用了IoStartPacket函數。就不要再碰IRP,因為在改函數返回前。IRP可能已經被完成並且其占用的內存可能被釋放,而我們擁有的該IRP的指針也許是無效的。

3:每處理一次IRP ,I/O管理器就調用一次StartIO例程。

4:當設備完成數據傳輸后。它將以硬件中斷形式發出通知。IoConnectInterrupt函數勾住一個中斷。該函數的一個參數就是ISR的地址。因此當中斷發生時。硬件抽象層(HAL)就調用你的ISR,ISR運行在DIRQL上。并由ISR專用的自旋鎖保護。一個ISR最可能做的事情就是調度DPC例程(推遲過程調用)。而DPC的目的就是讓你做某些事情。如調用IoCompleteRequest。而該調用不可能運行在ISR。運行在DIRQL級別上。

5:DPC例程的傳統名字為DpcForlser。因為它是由ISR請求的。DPCForlsr例程在DIsplatch-level級別上獲得控制。通常。它的工作就是完成IRP(導致最近的中斷發生)。但一般情況下。它通過調用IoCompleteRequest函數把剩餘的工作交給完成例程來做。

IOStartNextPacket取出設備隊列中的下一個IRP并發送到StartIO。False參數指出不能以通常方式取消。

IOCompleteRequest完成第一個參數指定的IRP。第二個參數是等待線程的優先級提高值。注意在調用IOCompleteRequest之前你還要填充IRP中的IoStatus塊。

調用IOCompleteRequest例程是處理I/O請求的標準結束方式。在這個調用之后,I/0管理器(或是任何在開始處創建該IRP的實體)將再次擁有該IRP,最後該IRP被這個實體銷毀并解除等待線程的阻塞狀態。

6:完成一個IRP必須先填充 IOStatus塊的status和Information成員。然後調用IoCompleteRequest例程。Status值就是Ntstatus.h中定義的狀態碼。而information值要取決于你完成的是何種類型的IRP以及是成功還是失敗。如果IRP完成失敗。你應該把Information域設置為0,如果你成功地完成了一個數據傳輸IRP。通常應該把Information域設置成傳輸的字節量。

PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(IRP);

ULONG code = stack->Parameters.DeviceIoControl.IoControlCode;

If(code==IOCTL_TOASTER_BOGUS)

Return CompleteRequest(Irp,Status_invalid_device_request,0);

completeRequest函數的Information參數類型為ULONG_PTR。即該參數即可以是一個ULONG也可以是一個指針。

7:WDM使用分層設備對象結構的目的就是使IRP能方便地從一層驅動程序傳遞到下一層驅動程序。

 Pdx->LowerDeviceObject = IoAttachDeviceToDeviceStack(fdo,pdo);

Fdo是設備對象地址。Pdo是處於設備堆棧底部的物理設備對象地址。IoAttachDeviceToDeviceStack函數返回給你下一層設備對象的地址。當你決定把上層收到的IRP發送給自己的下層時。那么這個設備對象就是調用IOCallDrive

向下傳遞一個IRP你有責任初始化一個IO_Stack_Location結構。下層的驅動程序將使用改結構獲取它的參數。一種方法是執行物理拷貝。

----

IoCopyCurrentIrpStackLocationToNext(IRP);

Status = IoCallDriver(pdx->LowerDeviceObject,Irp);

IoCopyCurrentIrpStackLocationToNext把當前堆棧單元的所有域,除了一個屬於I/O完成例程的域。都複製到下一個堆棧單元。

如果你的驅動程序不用關心IRP傳遞到下層驅動程序之後的事情,你可以利用一個捷徑來避免複製堆棧單元。我們就不需要安裝完成例程。沒有必要花費處理器時間去把你的堆棧單元內容複製到下一個堆棧單元,因為那個堆棧單元已經含有下一層驅動程序要得到的參數。以及自己下一層驅動程序可能給出的任何例程指針。因此。你可以使用下面的捷徑。

NTSTATUS ForwordAndForget(PDEVICE_OBJECT fdo,PIRP Irp)

{

PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION)fdo->DeviceExtension;

IoSkipCurrentIrpStackLocation(Irp);

Return IoCallDriver(pdx->LowerDeviceObject,Irp);


}

這個捷徑存在于IoSkipCurrentIrpStackLocation函數中。它實際上是一個宏,這個宏的作用就是使堆棧指針少前進一步。而IOCallDriver函數會使堆棧指針前進一步。中和的結果就是堆棧指針不變。當下一個驅動程序的派遣例程調用IoGetCurrentIrpStackLocation時。它將收到與我們正使用的完全相同的IO_STACK_LOCATION指針,因此,它所處理的將是同一個請求。相同的主副功能代碼。以及相同的參數。

8:取消I/O請求。

程序有時會取消他們原來請求的IRP。應用程序可能發出某些需要長時間才能完成的請求。然後這個應用程序結束執行。而這個IRP仍然是未完成的。這種情況在WDM模型中尤為常見。例如當新硬件插入系統時。驅動程序必須停止執行以等待配置管理器重新分配硬件資源。設備電源關閉時也是這樣。為了在內核模式中取消一個請求。IRP的創建者需使用IoCancelIrp函數。如果某線程終止時。它發出的請求仍然未完成。則操作系統自動為某個IRP調用IoCancelIrp.用戶模式應用程序調用CancelIo函數可以取消給定線程發出的所有未完成的異步操作。IoCancelIrp僅僅是簡單的設置IRP的Cancel標誌位。然後調用Irp的取消例程(它并不知道你時候修改過IRP指針,也不知道你是否正在處理這個IRP,所以它必須依靠一個你提供的取消例程來做大部分IRP取消工作)。

9:取消自旋鎖

Void StartIo(PDEVICE_OBJECT fdo,PIRP Irp)

{

KIRQL oldirql;

IoAcquireCancelSpinLock(&oldirql);

If(Irp!=fdo->CurrentIrp||Irp->Cancel)

{

          IoReleaseCancelSpinLock(oldirql);

          Return;

}

Else

{

          IoSetCancelRoutine(Irp,NULL);

          IoReleaseCancelSpinLock(oldirql);

}

}

Void OnCancel(PDEVICE_OBJECT fdo,PIRP Irp)

{

   If(fdo->CurrentiIrp==Irp)

{

   KIRQL oldirql = Irp->CancelIrql;

   IoReleaseCancelSpinLock(DISPATCH_LEVEL);

IoStartNextPacket(fdo,TRUE);

KeLowerIrql(oldiral);

}

Else

{

   KeRemoveEntryDeviceQueue(&fdo->DeviceQueue,&Irp->Tail.Overlay.DeviceQueueEntry);

IoReleaseCancelSpinLock(Irp->cancelIrpl);

}

CompleteRequest(Irp,STATUS_CANCELLED,0);

}

避免使用全局取消自旋鎖

9:創建自己的IRP

有四種不同的服務函數可以用來創建IRP。但我不得不推遲到現在才討論如何選擇它們。

(1:IoBuildAsynchronousFsdRequest和IoBuildSynchronousFsdRequest函數僅能用於創建主功能碼的IRP(IRP_MJ_READ,IRP_MJ_WRITE,IRP_MJ_FLUSH_BUFFER,IRP_MJ_SHUTDOWN,IRP_MJ_PNP,IRP_MJ_POWER);

IoBulidSynchronousFsdRequest(MajorFunction,DeviceObject,Buffer,Length,StartingOffset,Event,IoStatusBlock);

MajorFunction是新Irp的主功能嗎,DeviceObject是該IRP最初要發送到的設備對象的地址。對於讀寫請求。你必須提供Buffer(PVOID),Lengh(ULONG),StartingOffset是讀寫操作在目標文件中的定位。對於該函數創建的其它請求。這些參數將被忽略。Event 是一個事件對象的地址。IocompleteRequest應該是操作完成時設置這個事件。IostatusBlock是一個狀態塊的地址。該狀態塊用於保存IRP結束狀態和信息。在操作完成前。事件對象和狀態快必須一直存在與內存中。

如果創建的是讀寫IRP。那么在提交該IRP前你不需要做任何事,如果創建的是其它類型的IRP你需要用附加的參數信息完成第一個堆棧單元。

提交IRP并等待其完成:

PIRP Irp = IoBuildSynchronousFsdRequest(….);

NTSTATUS status = IOCallDriver(DeviceObject,Irp);

If(Status ==STATUS_PENDING)

KeWaitForSingleObject(Event,Executive,KernelMode,False,NULL):

IRP完成后,你可以通過查看你的I/O狀態快來了解該IRP的結束狀態和相關信息。

10:清除

你必須事先計劃好IRP占用內存的釋放以及IRP的取消。當使用IOBuildSynRonOusFsdRequest創建IRP時。I/O管理器自動釋放IRP占用的內存。如果是需要系統緩衝區或內存描述符的讀寫請求。I/O管理器也能自動清除。

系統中僅有兩個實體可以取消IRP,一個是I/O管理器中稱為thread rundown的代碼,當線程結束仍有未處理的IRP。就執行這段代碼。另一個實體就是產生該Irp的驅動程序

即插即用

1:在 WDM中。Pnp請求扮演了兩個角色。在第一個角色中。這些請求指示驅動程序何時以及如何配置或取消其硬件或自身的設置。Pnp管理器使用 IRP_MN_START_DEVICE來通知功能驅動程序其硬件被賦予了設么I/O資源。以及指導功能驅動程序做任何必要的硬件或軟件設置以便設備能正常工作。IRP_MN_STOP_DEVICE告訴功能驅動程序關閉設備。IRP_MN_REMOVE_DEVICE告訴功能驅動程序關閉設備并釋放與之關聯的設備對象。PNP 請求的第二個角色是指導驅動程序完成一系列狀態裝換。

2:啟動和停止設備

通過使用總線驅動程序。Pnp管理器能夠自動檢測硬件和分配I/O資源。大部分現代設備都有即插即用特性。可以允許系統軟件自動檢測并提取它們的I/O資源需求。WDM包含四種標準I/O資源類型:I/O端口,內存寄存器,DMA通道,中斷請求。

當PNP管理器檢測到硬件時。它首先參考注冊表以了解有哪些過濾器驅動程序將管理該硬件。

開始,pnp管理器為每個設備創建一個資源需求列表并允許驅動程序過濾這個列表。一旦資源分配確定。Pnp管理器通過向每個設備發送一個帶IRP_MN_START_DEVICE副功能嗎的pnp請求來通知設備。通常過濾器驅動程序對這個IRP不感興趣。所以他們使用DefaultPnpHandler方式把請求向下傳,而功能驅動程序正好相反。它需要在這個IRP上做大量的工作。包括分配并配置額外的軟件資源以及為設備操作做準備。這個工作需要在PASSIVE_LEVEL級上進行。并在低層驅動程序處理完該IRP后完成。爲了在下傳IRP_MN_START_DEVICE請求后再獲得控制。派遣例程需要等待一個內核事件。該事件最終由低層驅動程序對IRP的完成操作做通知。

3:總線驅動程序利用IoStatus.Status中的設置來判斷上層驅動程序是否已經處理了該IRP,對於IRP_MJ_PNP的其他幾個副功能嗎。總線驅動程序也做過類似的判斷。IRP_MN_STOP_DEVICE設備停止請求通知你關閉設備。然後Pnp管理器重新分配I/O資源。在硬件級關閉設備將包括暫停或停止當前活動并阻止後來的中斷。在軟件級關閉設備將涉及釋放設備啟動時配置的I/O資源。當設備將要被系統刪除時。Pnp管理器向你發送副功能嗎為IRP_MN_REMOVE_DEVICE的pnp請求。

NTSTATUS HandleRemoveDevice(PDEVICE_OBJECT fdo,PIRP Irp)

{

   PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION)fdo->DeviceExtension;

DeregisterAllInterfaces(pdx);

StopDevice(fdo,oktouch);

Irp->IoStatus.status = STATUS_SUCCESS;

NTSTATUS status = DefaultPnpHandler(fdo,Irp);

RemoveDevice(fdo);

Return status;

}

Void RemoveDevice(PDEVICE_OBJECT fdo)

{

   PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION)fdo->DeviceExtension;

IoDetachDevice(pdx->LowerDeviceObject);

IoDeleteDevice(fdo);

}

1:IoDetachDevice調用與AddDevice中的IoAttachDevice ToDeviceStack調用正好相反。

2:IoDeleteDevice調用與AddDevice中的IoCreateDevice調用正好相反。一旦該函數返回。設備對象將不復存在。如果你的驅動程序沒有其他設備需要管理,那么很快你的驅動程序也將從內存中卸載。

有時用戶可能不經過任何用戶接口交互操作突然地拆卸設備。如果系統檢測到這種突然的刪除。它就向驅動程序發送副功能嗎為IRP_MN_SURPRISE_REMOVAL的pnp請求。後面還會跟著一個IRP_MN_REMOVE_DEVICE請求。除非你以前在處理否則系統將顯示一個對話框。通知用戶這樣做很危險。爲了響應突然刪除請求。設備驅動程序應該禁止所有已寄存的接口。這將給應用程序一個機會關閉設備的句柄。但應用程序必須事先關注這種通知。

NTSTATUS HandleSurpriseRemoval(PDEVICE_OBJECT fdo,PIRP irp)

{

   PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION)fdo->DeviceExtension;

EnableAllInterfaces(pdx,FALSE);

StopDevice(fdo,oktouch);

Irp->IoStatus.Status = STATUS_SUCCESS;

Return DefaultPnpHandler(fdo,Irp);

}

IRP_MN_SURPRISE_REMOVEAL來自何處???

簡單並且直接地從計算機上拆卸設備并不能產生突然刪除pnp 通知。有些總線能夠察覺設備消失。例如拔掉一個USB設備將產生一個電信號。這個電信號能被總線驅動程序注意到。然而。對於大多數其他總線類型。沒有任何信號能用於通知總線驅動程序。因此。Pnp管理器需要依靠其他方法來判斷設備是否消失。

功能驅動程序可以通知其管理的設備的消失(如果它知道的話),通過調用IoInvalidateDeviceState函數。然後從緊接這的IRP_MN_QUERY_PNP_DEVICE_STATE中返回PNP_DEVICE_FAILED,PNP_DEVICE_REMOVED,PNP_DEVICE_DISABLED中的任一個值,比如。如果你的ISR在讀通常結果為1和0混合的狀態端口突然得到了全部為1的值,你的驅動程序就應該通知設備消失。更普通的情況是,總線驅動程序調用IoInvalidateDeviceRelations函數觸發一個再枚舉操作時報告某個設備枚舉失敗。另外,如果系統處於休眠或在其他低電源狀態時用戶拆卸了設備。那么驅動程序在接收到IRP_MN_SURRISE_REMOVAL請求前先收到了一系列電源管理IRP.這些事實說明了驅動程序應該能應付由設備突然消失所造成的錯誤。

3:可以使用一個DEVQUEUE來排隊和取消IRP

例如你的設備用一個單獨的隊列來管理讀寫請求。你應該定義一個DEVQUEUE

4:pnp管理器在停止你的設備前總是先詢問。得到允許后才向你發送IRP_MN_STOP_DEVICE請求。詢問以IRP_MN_QUERY_STOP_DEVICE請求的形式出現。你可以回答成功或失敗。詢問的基本含義是,如果系統在幾納秒后向你發送IRP_MN_STOP_DEVICE。你能立即停止設備嗎?你可以用兩種稍微不同的方式處理這個詢問請求。第一種方式適合可以迅速完成或者能容易地中途結束的IRP

NTSTATUS HandleQueryStop(PDEVICE_OBJECT fdo,PIRP  irp)

{

   Irp->IoStatus.Status= STATUS_SUCCESS;

   PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION)fdo->DeviceExtension;

If(pdx->status!=WORKING)

<--1>該語句用於這種特殊情況。Pnp管理器發Query_stop你希望忽略這樣這的查詢。

Return DefaultPnpHandler(fdo,Irp);

If(!OkayToStop(pdx))

{

  Return CompleteRequest(Irp,STATUS_UNSUCCESSFUL,0);

  StallRequests(&pdx->dqReadWrite);

  WaitForCurrentIrp(&pdx->dqReadWrite);

  Pdx->status = PENDINGSTOP;

  Return DefaultPnpHandler(fdo,Irp);

}

}

另一種處理QUERY_STOP的方式適合于需要長時間才能完成並且不能被中途停止的IRP,例如磁帶機的備份操作就不能被中途打斷。在這種情況下。你可以使用DEVQUEUE的checkbusyandstall函數。如果你的設備忙。該函數返回TRUE,在這種情況下。你還需要停止隊列(檢測設備狀態和停止隊列操作需要一個自旋鎖的保護)。

即使你成功的回答了查詢。但下層驅動程序可能會失敗這個查詢。即使所有的驅動程序都成功的回答了查詢。Pnp管理器也可能決定不關閉你的設備。在這種情況下。你將收到另一個副功能嗎為IRP_MN_CANCEL_STOP_DEVICE的pnp請求。它通知設備不將被關閉。之後你應該清除在查詢中設置的任何state值。

與pnp管理器在停止設備前向你詢問一樣。它在刪除設備前也會向你詢問。即IRP_MN_QUERY_REMOVE_DEVICE請求。你可以回答成功或失敗。與停止查詢相似。如果pnp管理器中途改變想法。它就發送IRP_MN_CANCEL_REMOVE_DEVICE請求。

防止設備過早地刪除的基本想法是在每一次開始處理請求時都獲取刪除鎖。處理完成后釋放刪除鎖。在你刪除你的設備對象前。應確保刪除鎖未被使用。否則,你將等到這個鎖的所有引用都被釋放。

5:如果用戶使用設備管理器刪除設備。而某應用程序正擁有該設備的打開句柄。那么操作系統將拒絕刪除設備并通知用戶。如果設備被用戶從計算機上物理的摘除並且沒有使用設備管理器。那么一個良好的應用程序應該注意WM_DEVICECHANGE消息。該消息通知應用程序設備已經被卸載。應用程序接著應該關閉設備句柄。驅動程序請求。直到句柄被真正的關閉。其實這也是刪除鎖邏輯允許你做的。

6:設備用途通知

磁盤驅動程序(以及磁盤控制器驅動程序)有時候需要了解外來的關於它們如何被操作系統使用的信息。IRP_MN_DEVICE_NOTIFICATION請求提供了獲取這些信息的手段。在IRP堆棧單元的Parameters.UsageNotification子結構中包含了這樣兩個參數,(InPath如果設備處於Type指定的路徑中,則為TRUE,否則為FALSE,TYPE用法類型)在通知請求的子派遣例程中。你應該用一個Switch語句來區分各種通知。在大多數情況下。你將把該IRP下傳。下面是這個子派遣例程的框架代碼。

NTSTATUS HandleUsageNotification(PDEVICE_OBJECT fdo,PIRP Irp)

{

    PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION)fdo->DeviceExtension;

PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(Irp);

DEVICE_USAGE_NOTIFICATION_TYPE type = stack->Parameters.UsageNotification.Inpath;

Switch(type)

{

   Case DeviceUsageTypeHibernation:

    ---

    Irp->IoStatus.Status=STATUS_SUCCESS;

    Break:

   Case DeviceUsageTypeDumpFile:

   ---

   Irp->IoStatus.Status = STATUS_SUCCESS;

   Break;

   Case DeviceUsageTypePaging:

   ---

   Irp->IoStatus.Status = STATUS_SUCCESS;

   Break;

Default:

  Break;

}

Return DefaultPnpHandler(fdo,Irp);

}

僅當你明確的認為該通知是送往總線驅動程序的信號時才設置其Status域為STATUS_SUCCESS。如果你沒有設置STATUS_SUCCESS.則總線驅動程序將假設你并不知道。因此也不處理該通知。你應該知道你的設備不能支持某種用途。例如。假設你的磁盤設備不能用來存儲休眠文件。但如果該IRP指定InPath值。你應該使該IRP失敗。

-----

Case DeviceUsageTypeHibernation:

    If(inPath)

     Return CompleteRequest(Irp,STATUS_UNSUCCESSFUL,0);

DeviceUsageTypePaging如果該通知為Inpath為TRUE則指出將有一個內存交換文件在這個設備上打開。如果為FALSE則指出有一個內存交換文件已被關閉。

DeviceUsageTypeDumpFile如果該通知的InPath為TRUE則指出設備已被選定用於保存系統崩潰時的DUMP文件。如果為False則取消這個設定。

DeviceUsageTypeHibernation如果該通知的InPath為TRUE則指出設備被選定用於保存休眠文件。如果為FALSE則取消這個選定。

7:windows2000提供了一個方法來通知用戶模式和內核模式部件剛發生的PNP事件。Windows95有一個WM_DEVICECHANGE消息。用戶模式可以通過處理該消息來監視。或控制系統中的硬件和電源配置的改變。新操作系統中的WM_DEVICECHANGE消息還允許用戶模式程序容易地檢測到某驅動程序允許或禁止寄存的設備接口。內核模式也可以注冊類似的通知。參見SDK中關於WM_DEVICECHANGE,RegisterDeviceNotification,UnRegisterDeviceNotification的文檔。