驅動開發學習筆記1
1:設(shè)備對象是系統(tǒng)為幫組軟件管理硬件而創(chuàng)建的數(shù)據(jù)結(jié)構(gòu),一個物理硬件可以有多個這樣的數(shù)據(jù)結(jié)構(gòu)。處於堆棧最底層的設(shè)備對象稱為物理設(shè)備對象(PDO);
2:操作系統(tǒng)的pnp管理器按照設(shè)備驅(qū)動程序的要求構(gòu)造了設(shè)備對象堆棧。總線驅(qū)動程序的任務(wù)就是枚舉總線上的設(shè)備。并為每個設(shè)備創(chuàng)建一個PDO;一旦總線驅(qū)動程序檢查到新硬件存在。Pnp管理器就創(chuàng)建一個PDO.創(chuàng)建完P(guān)DO后。PNP管理器參照注冊表中的信息查找于這個PDO相關(guān)的過濾器和功能驅(qū)動程序。系統(tǒng)安裝程序負(fù)責(zé)添加這些注冊表項。而驅(qū)動程序包中的控制硬件安裝的INF文件負(fù)責(zé)添加其他表項。這些表項定義了過濾器和功能驅(qū)動程序在堆棧中的次序。
PNP管理器先裝入最底層的過濾器驅(qū)動程序并調(diào)用其AddDevice函數(shù)。這個函數(shù)創(chuàng)建一個FIDO。這樣在過濾器驅(qū)動程序和FIDO之間建立了水平連接。然後ADDDevice把PDO連接到FIDO上。這就是設(shè)備對象之間連線的由來。PNP管理器繼續(xù)向上執(zhí)行。裝入并調(diào)用每個底層過濾器,功能驅(qū)動程序,每個高層過濾器,直到完成整個堆棧。
3:通常IRP先被送到設(shè)備堆棧的最上層驅(qū)動程序。然後逐步的過濾到下面的驅(qū)動程序。每一層驅(qū)動程序都可以決定如何處理IRP,有時候驅(qū)動程序不做任何事情。只是向下層穿IRP。有時。驅(qū)動程序直接處理完該IRP不再向下傳遞。還有時。驅(qū)動程序處理了IRP又把IRP傳遞下去。這取決于設(shè)備以及IRP所攜帶的內(nèi)容。
4:系統(tǒng)這么裝入驅(qū)動程序?
既然總線驅(qū)動程序創(chuàng)建了PDO。然後PDO管理器根據(jù)改PDO的注冊表項裝入它的驅(qū)動程序。那么總線驅(qū)動程序從哪裡來???
首先,PNP管理器有一個內(nèi)建的驅(qū)動程序。它與一個實際不存在的根總線相對應(yīng)。根總線概念性的把計算機與所有那些不能用電子方式申明自己存在的設(shè)備連接起來。這包括主硬件總線(如PCI)。根總線驅(qū)動程序從注冊表中獲取有關(guān)計算機的信息。而這些關(guān)於計算機本身的注冊表信息是由windows系統(tǒng)安裝程序初始化的。安裝程序通過運行一個盡心製作的硬件檢測程序以及向用戶提出一些適當(dāng)?shù)膯栴}來獲取這些信息。所以,根總線驅(qū)動程序有足夠的信息為主總線創(chuàng)建PDO.然後,主總線的功能驅(qū)動程序用電子方式枚舉自己的硬件。
5:有三種注冊表鍵負(fù)責(zé)配置。它們是硬件鍵。類鍵。服務(wù)建。硬件鍵包括單個設(shè)備的信息。類鍵涉及到所有相同類型設(shè)備的共同信息。服務(wù)鍵包含驅(qū)動程序信息。一般注冊表中都會包含一些以前用過的和現(xiàn)在用過的硬件信息。
設(shè)備的硬件鍵出現(xiàn)在注冊表local machine分支的\system\currentControlSet\Enum子鍵上。Enum下的第一級子鍵與系統(tǒng)中的各種總線枚舉器相對應(yīng)。SetupDixXXX函數(shù)可以訪問Enum鍵。
假設(shè)你的驅(qū)動程序使用IoRegisterDeviceInterface函數(shù)注冊了一個設(shè)備接口。並且你有一個改接口的符號連接名(通過枚舉該接口GUID的所有實例或從WM_DeviceChange消息的參數(shù)中獲得這個名字)。
類鍵,所有設(shè)備類的類鍵都出現(xiàn)在HKLM\system\currentcontrolset\control\class鍵中。每個設(shè)備下還可以在所屬類鍵下?lián)碛凶约旱淖渔I。該鍵的鍵名就是設(shè)備硬件鍵中的Driver值。這個子鍵的作用是把所有這些注冊表項與安裝設(shè)備使用的INF文件關(guān)聯(lián)。
服務(wù)建:HKLM\System\CurrentControlSet\Services鍵中。
ImagePath:指出驅(qū)動程序執(zhí)行文件的名字和路徑。
6:當(dāng)說系統(tǒng)裝入一個驅(qū)動程序時。是指系統(tǒng)把驅(qū)動程序的映像映射到虛擬內(nèi)存中。并重定位內(nèi)存參考。最後調(diào)用驅(qū)動程序的主入口點。如果驅(qū)動程序已經(jīng)在內(nèi)存中。則裝入過程僅僅是增加驅(qū)動程序映像的參考計數(shù)。
7:I/O管理器適用驅(qū)動程序?qū)ο髞泶砻總€設(shè)備驅(qū)動程序。
WDM驅(qū)動程序可以調(diào)用IOCreateDevice函數(shù)創(chuàng)建設(shè)備對象。但設(shè)備對象的管理則由I/O管理器負(fù)責(zé)。
DriverObject指向與該設(shè)備對象相關(guān)的驅(qū)動程序?qū)ο蟆Mǔ>褪钦{(diào)用IOCreateDevice函數(shù)創(chuàng)建該設(shè)備對象的驅(qū)動程序?qū)ο蟆extDevice指向?qū)凫锻粋€驅(qū)動程序的下一個設(shè)備對象。CurrentIrp指向最近發(fā)往驅(qū)動程序StartIO函數(shù)的I/O請求包。
Device_Object結(jié)構(gòu)的Flags標(biāo)誌:
這只列舉主要的幾個
DO_BUFFERD_IO:讀寫操作適用緩沖方式(系統(tǒng)複製緩衝區(qū))。
DO_DIRECT_IO:讀寫操作適用直接方式(內(nèi)存描述符表)
8:如何建立設(shè)備堆棧???
NextDevice域把所有屬於特定驅(qū)動程序的設(shè)備對象水平的連接在一起。而垂直方向上的鏈接主要用不透明域。從PDO開始。每個設(shè)備對象都有指向上一層設(shè)備對象的指針。由於沒有已公開的向下方向的指針。所以驅(qū)動程序必須自己記住下層設(shè)備對象是誰。
每個wdm驅(qū)動程序必須能處理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驅(qū)動程序需要在DriverEntry例程中枚舉他們的硬件,并在其硬件的所有可能實例被識別前裝入內(nèi)存并初始化。例如。鼠標(biāo)和鍵盤就是這樣的。但如果DriverEntry例程運行的太快。那么這些驅(qū)動程序?qū)⒉荒苷9ぷ鳌R虼?#xff0c;它們必須使用IORegisterDriverReinitialization函數(shù)寄存一個例程。之後I/O管理器在某個驅(qū)動程序檢測到新硬件存在時再回調(diào)這個寄存例程。最後在初始化例程運行。同時也把自身寄存為下一個回調(diào)的函數(shù)。
WDM驅(qū)動程序不需要寄存再初始化例程。因為它們不需要用自己的代碼去檢測硬件。Pnp管理器自動把新硬件匹配到正確的WDM驅(qū)動程序上。并調(diào)用改驅(qū)動的ADDDevice例程。再由AddDevice例程做所有必要的初始化工作。
對於功能驅(qū)動程序。其AddDevice函數(shù)的基本職責(zé)是創(chuàng)建一個設(shè)備對象并把它連接到以pdo為底的設(shè)備堆棧中。相關(guān)步驟
(1:調(diào)用IOCreateDevice創(chuàng)建設(shè)備對象,并建立一個私有的設(shè)備擴展對象。
(2:注冊一個多多個設(shè)備接口,以便應(yīng)用程序能知道設(shè)備的存在。另外。還可以給出設(shè)備名并創(chuàng)建符號連接。
(3:初始化設(shè)備擴展和設(shè)備對象的flag成員。
(4:調(diào)用IoAttachDeviceToDeviceStack函數(shù)把新設(shè)備對象放到堆棧上。
10:用戶模式程序可以用DefineDosDevice創(chuàng)建一個符號連接。如下:
BOOL? okay = DefineDosDevice(DDD_RAW_TARGET_PATH,”barf”,”\\Device\\SECTEST_0”);
在WDM中IOCreateSymbolicLink(Linkname,targname);
11:應(yīng)該命名設(shè)備對象嗎???
如果命名了設(shè)備對象。那么任何內(nèi)核模式程序都可以打開改設(shè)備的句柄。另外。任何內(nèi)核模式或用戶模式都能創(chuàng)建連接到該設(shè)備的符號連接。并可以使用這個符號連接打開設(shè)備的句柄。
12:建立設(shè)備堆
每個過濾器驅(qū)動程序和功能驅(qū)動程序都有責(zé)任把設(shè)備對象放到設(shè)備堆棧上。從PDO開始一直向上。
PDEVICE_OBJECT fdo;
IoCreateDevice(…,&fdo);
Pdx->LowerDeviceObject = IoAttachDeviceToDeviceStack(fdo,pdo);
13:清除DO_DEVICE_INITIALIZING標(biāo)誌
在AddDevice中最後一件要做的事情是清除設(shè)備對象中的DO_Device_initiatizing標(biāo)誌、
Fdo->Flags&=~DO_DEVICE_INITIALIZING;
當(dāng)這個標(biāo)誌設(shè)置時。I/O管理器將拒絕任何打開改設(shè)備句柄的請求或向設(shè)備對象上附著其他設(shè)備對象的請求。在驅(qū)動程序完成初始化后。必須清除這個標(biāo)誌。
1:創(chuàng)建完IRP后。你可以調(diào)用IOGetNextIrpStackLocation函數(shù)獲得該IRP第一個堆棧單元的指針。然後初始化這個堆棧單元。在初始化過程的最後,你需要填充MajorFunction代碼。堆棧單元初始化完成后,就可以調(diào)用IoCallDriver函數(shù)把irp發(fā)送到設(shè)備驅(qū)動程序。
PDEVICE_OBJECT DeviceObject;
PIO_STACK_LOCATION stack = IOGetNextIrpStackLoction(Irp);
Stack->MajorFunction = IRP_MJ_Xxx;
NTSTATUS status = IoCallDriver(DeviceObject,irp);
2:當(dāng)有大量讀寫請求進(jìn)入設(shè)備時,通常需要把這些請求放入一個隊列中。以便使硬件訪問串行化。每個設(shè)備對象都有一個請求隊列對象。下面是使用這個隊列的標(biāo)準(zhǔn)方法。
NTSTATUS DispatchXxx(…)
{
??? ….
??? IoMarkIrpPending(IRP);
??? IoStartPacket(device,irp,NULL,NULL);
??? Return STATUS_PENDING;
}
(1:無論何時。如果當(dāng)派遣函數(shù)返回STATUS_PENDING狀態(tài)代碼的時候,你應(yīng)該先調(diào)用這個IoMarkIrpPending函數(shù)。以幫組IO管理器避免內(nèi)部競爭,我們必須在放棄IRP所有權(quán)之前做這一點。
如果設(shè)備正忙。那么IoStartPacket就把請求放到隊列中。如果設(shè)備空閒,IoStartPacket將把設(shè)備置成忙并調(diào)用StartIO例程。
返回Stauts_pending通知調(diào)用者我們沒有完成這個IRP;
注意一旦我們調(diào)用了IoStartPacket函數(shù)。就不要再碰IRP,因為在改函數(shù)返回前。IRP可能已經(jīng)被完成並且其占用的內(nèi)存可能被釋放,而我們擁有的該IRP的指針也許是無效的。
3:每處理一次IRP ,I/O管理器就調(diào)用一次StartIO例程。
4:當(dāng)設(shè)備完成數(shù)據(jù)傳輸后。它將以硬件中斷形式發(fā)出通知。IoConnectInterrupt函數(shù)勾住一個中斷。該函數(shù)的一個參數(shù)就是ISR的地址。因此當(dāng)中斷發(fā)生時。硬件抽象層(HAL)就調(diào)用你的ISR,ISR運行在DIRQL上。并由ISR專用的自旋鎖保護。一個ISR最可能做的事情就是調(diào)度DPC例程(推遲過程調(diào)用)。而DPC的目的就是讓你做某些事情。如調(diào)用IoCompleteRequest。而該調(diào)用不可能運行在ISR。運行在DIRQL級別上。
5:DPC例程的傳統(tǒng)名字為DpcForlser。因為它是由ISR請求的。DPCForlsr例程在DIsplatch-level級別上獲得控制。通常。它的工作就是完成IRP(導(dǎo)致最近的中斷發(fā)生)。但一般情況下。它通過調(diào)用IoCompleteRequest函數(shù)把剩餘的工作交給完成例程來做。
IOStartNextPacket取出設(shè)備隊列中的下一個IRP并發(fā)送到StartIO。False參數(shù)指出不能以通常方式取消。
IOCompleteRequest完成第一個參數(shù)指定的IRP。第二個參數(shù)是等待線程的優(yōu)先級提高值。注意在調(diào)用IOCompleteRequest之前你還要填充IRP中的IoStatus塊。
調(diào)用IOCompleteRequest例程是處理I/O請求的標(biāo)準(zhǔn)結(jié)束方式。在這個調(diào)用之后,I/0管理器(或是任何在開始處創(chuàng)建該IRP的實體)將再次擁有該IRP,最後該IRP被這個實體銷毀并解除等待線程的阻塞狀態(tài)。
6:完成一個IRP必須先填充?IOStatus塊的status和Information成員。然後調(diào)用IoCompleteRequest例程。Status值就是Ntstatus.h中定義的狀態(tài)碼。而information值要取決于你完成的是何種類型的IRP以及是成功還是失敗。如果IRP完成失敗。你應(yīng)該把Information域設(shè)置為0,如果你成功地完成了一個數(shù)據(jù)傳輸IRP。通常應(yīng)該把Information域設(shè)置成傳輸?shù)淖止?jié)量。
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函數(shù)的Information參數(shù)類型為ULONG_PTR。即該參數(shù)即可以是一個ULONG也可以是一個指針。
7:WDM使用分層設(shè)備對象結(jié)構(gòu)的目的就是使IRP能方便地從一層驅(qū)動程序傳遞到下一層驅(qū)動程序。
?Pdx->LowerDeviceObject = IoAttachDeviceToDeviceStack(fdo,pdo);
Fdo是設(shè)備對象地址。Pdo是處於設(shè)備堆棧底部的物理設(shè)備對象地址。IoAttachDeviceToDeviceStack函數(shù)返回給你下一層設(shè)備對象的地址。當(dāng)你決定把上層收到的IRP發(fā)送給自己的下層時。那么這個設(shè)備對象就是調(diào)用IOCallDrive
向下傳遞一個IRP你有責(zé)任初始化一個IO_Stack_Location結(jié)構(gòu)。下層的驅(qū)動程序?qū)⑹褂酶慕Y(jié)構(gòu)獲取它的參數(shù)。一種方法是執(zhí)行物理拷貝。
----
IoCopyCurrentIrpStackLocationToNext(IRP);
Status = IoCallDriver(pdx->LowerDeviceObject,Irp);
IoCopyCurrentIrpStackLocationToNext把當(dāng)前堆棧單元的所有域,除了一個屬於I/O完成例程的域。都複製到下一個堆棧單元。
如果你的驅(qū)動程序不用關(guān)心IRP傳遞到下層驅(qū)動程序之後的事情,你可以利用一個捷徑來避免複製堆棧單元。我們就不需要安裝完成例程。沒有必要花費處理器時間去把你的堆棧單元內(nèi)容複製到下一個堆棧單元,因為那個堆棧單元已經(jīng)含有下一層驅(qū)動程序要得到的參數(shù)。以及自己下一層驅(qū)動程序可能給出的任何例程指針。因此。你可以使用下面的捷徑。
NTSTATUS ForwordAndForget(PDEVICE_OBJECT fdo,PIRP Irp)
{
PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION)fdo->DeviceExtension;
IoSkipCurrentIrpStackLocation(Irp);
Return IoCallDriver(pdx->LowerDeviceObject,Irp);
}
這個捷徑存在于IoSkipCurrentIrpStackLocation函數(shù)中。它實際上是一個宏,這個宏的作用就是使堆棧指針少前進(jìn)一步。而IOCallDriver函數(shù)會使堆棧指針前進(jìn)一步。中和的結(jié)果就是堆棧指針不變。當(dāng)下一個驅(qū)動程序的派遣例程調(diào)用IoGetCurrentIrpStackLocation時。它將收到與我們正使用的完全相同的IO_STACK_LOCATION指針,因此,它所處理的將是同一個請求。相同的主副功能代碼。以及相同的參數(shù)。
8:取消I/O請求。
程序有時會取消他們原來請求的IRP。應(yīng)用程序可能發(fā)出某些需要長時間才能完成的請求。然後這個應(yīng)用程序結(jié)束執(zhí)行。而這個IRP仍然是未完成的。這種情況在WDM模型中尤為常見。例如當(dāng)新硬件插入系統(tǒng)時。驅(qū)動程序必須停止執(zhí)行以等待配置管理器重新分配硬件資源。設(shè)備電源關(guān)閉時也是這樣。為了在內(nèi)核模式中取消一個請求。IRP的創(chuàng)建者需使用IoCancelIrp函數(shù)。如果某線程終止時。它發(fā)出的請求仍然未完成。則操作系統(tǒng)自動為某個IRP調(diào)用IoCancelIrp.用戶模式應(yīng)用程序調(diào)用CancelIo函數(shù)可以取消給定線程發(fā)出的所有未完成的異步操作。IoCancelIrp僅僅是簡單的設(shè)置IRP的Cancel標(biāo)誌位。然後調(diào)用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:創(chuàng)建自己的IRP
有四種不同的服務(wù)函數(shù)可以用來創(chuàng)建IRP。但我不得不推遲到現(xiàn)在才討論如何選擇它們。
(1:IoBuildAsynchronousFsdRequest和IoBuildSynchronousFsdRequest函數(shù)僅能用於創(chuàng)建主功能碼的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最初要發(fā)送到的設(shè)備對象的地址。對於讀寫請求。你必須提供Buffer(PVOID),Lengh(ULONG),StartingOffset是讀寫操作在目標(biāo)文件中的定位。對於該函數(shù)創(chuàng)建的其它請求。這些參數(shù)將被忽略。Event 是一個事件對象的地址。IocompleteRequest應(yīng)該是操作完成時設(shè)置這個事件。IostatusBlock是一個狀態(tài)塊的地址。該狀態(tài)塊用於保存IRP結(jié)束狀態(tài)和信息。在操作完成前。事件對象和狀態(tài)快必須一直存在與內(nèi)存中。
如果創(chuàng)建的是讀寫IRP。那么在提交該IRP前你不需要做任何事,如果創(chuàng)建的是其它類型的IRP你需要用附加的參數(shù)信息完成第一個堆棧單元。
提交IRP并等待其完成:
PIRP Irp = IoBuildSynchronousFsdRequest(….);
NTSTATUS status = IOCallDriver(DeviceObject,Irp);
If(Status ==STATUS_PENDING)
KeWaitForSingleObject(Event,Executive,KernelMode,False,NULL):
IRP完成后,你可以通過查看你的I/O狀態(tài)快來了解該IRP的結(jié)束狀態(tài)和相關(guān)信息。
10:清除
你必須事先計劃好IRP占用內(nèi)存的釋放以及IRP的取消。當(dāng)使用IOBuildSynRonOusFsdRequest創(chuàng)建IRP時。I/O管理器自動釋放IRP占用的內(nèi)存。如果是需要系統(tǒng)緩衝區(qū)或內(nèi)存描述符的讀寫請求。I/O管理器也能自動清除。
系統(tǒng)中僅有兩個實體可以取消IRP,一個是I/O管理器中稱為thread rundown的代碼,當(dāng)線程結(jié)束仍有未處理的IRP。就執(zhí)行這段代碼。另一個實體就是產(chǎn)生該Irp的驅(qū)動程序
即插即用
1:在 WDM中。Pnp請求扮演了兩個角色。在第一個角色中。這些請求指示驅(qū)動程序何時以及如何配置或取消其硬件或自身的設(shè)置。Pnp管理器使用?IRP_MN_START_DEVICE來通知功能驅(qū)動程序其硬件被賦予了設(shè)么I/O資源。以及指導(dǎo)功能驅(qū)動程序做任何必要的硬件或軟件設(shè)置以便設(shè)備能正常工作。IRP_MN_STOP_DEVICE告訴功能驅(qū)動程序關(guān)閉設(shè)備。IRP_MN_REMOVE_DEVICE告訴功能驅(qū)動程序關(guān)閉設(shè)備并釋放與之關(guān)聯(lián)的設(shè)備對象。PNP 請求的第二個角色是指導(dǎo)驅(qū)動程序完成一系列狀態(tài)裝換。
2:啟動和停止設(shè)備
通過使用總線驅(qū)動程序。Pnp管理器能夠自動檢測硬件和分配I/O資源。大部分現(xiàn)代設(shè)備都有即插即用特性。可以允許系統(tǒng)軟件自動檢測并提取它們的I/O資源需求。WDM包含四種標(biāo)準(zhǔn)I/O資源類型:I/O端口,內(nèi)存寄存器,DMA通道,中斷請求。
當(dāng)PNP管理器檢測到硬件時。它首先參考注冊表以了解有哪些過濾器驅(qū)動程序?qū)⒐芾碓撚布?/p>
開始,pnp管理器為每個設(shè)備創(chuàng)建一個資源需求列表并允許驅(qū)動程序過濾這個列表。一旦資源分配確定。Pnp管理器通過向每個設(shè)備發(fā)送一個帶IRP_MN_START_DEVICE副功能嗎的pnp請求來通知設(shè)備。通常過濾器驅(qū)動程序?qū)@個IRP不感興趣。所以他們使用DefaultPnpHandler方式把請求向下傳,而功能驅(qū)動程序正好相反。它需要在這個IRP上做大量的工作。包括分配并配置額外的軟件資源以及為設(shè)備操作做準(zhǔn)備。這個工作需要在PASSIVE_LEVEL級上進(jìn)行。并在低層驅(qū)動程序處理完該IRP后完成。爲(wèi)了在下傳IRP_MN_START_DEVICE請求后再獲得控制。派遣例程需要等待一個內(nèi)核事件。該事件最終由低層驅(qū)動程序?qū)RP的完成操作做通知。
3:總線驅(qū)動程序利用IoStatus.Status中的設(shè)置來判斷上層驅(qū)動程序是否已經(jīng)處理了該IRP,對於IRP_MJ_PNP的其他幾個副功能嗎。總線驅(qū)動程序也做過類似的判斷。IRP_MN_STOP_DEVICE設(shè)備停止請求通知你關(guān)閉設(shè)備。然後Pnp管理器重新分配I/O資源。在硬件級關(guān)閉設(shè)備將包括暫停或停止當(dāng)前活動并阻止後來的中斷。在軟件級關(guān)閉設(shè)備將涉及釋放設(shè)備啟動時配置的I/O資源。當(dāng)設(shè)備將要被系統(tǒng)刪除時。Pnp管理器向你發(fā)送副功能嗎為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調(diào)用與AddDevice中的IoAttachDevice ToDeviceStack調(diào)用正好相反。
2:IoDeleteDevice調(diào)用與AddDevice中的IoCreateDevice調(diào)用正好相反。一旦該函數(shù)返回。設(shè)備對象將不復(fù)存在。如果你的驅(qū)動程序沒有其他設(shè)備需要管理,那么很快你的驅(qū)動程序也將從內(nèi)存中卸載。
有時用戶可能不經(jīng)過任何用戶接口交互操作突然地拆卸設(shè)備。如果系統(tǒng)檢測到這種突然的刪除。它就向驅(qū)動程序發(fā)送副功能嗎為IRP_MN_SURPRISE_REMOVAL的pnp請求。後面還會跟著一個IRP_MN_REMOVE_DEVICE請求。除非你以前在處理否則系統(tǒng)將顯示一個對話框。通知用戶這樣做很危險。爲(wèi)了響應(yīng)突然刪除請求。設(shè)備驅(qū)動程序應(yīng)該禁止所有已寄存的接口。這將給應(yīng)用程序一個機會關(guān)閉設(shè)備的句柄。但應(yīng)用程序必須事先關(guān)注這種通知。
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來自何處???
簡單並且直接地從計算機上拆卸設(shè)備并不能產(chǎn)生突然刪除pnp?通知。有些總線能夠察覺設(shè)備消失。例如拔掉一個USB設(shè)備將產(chǎn)生一個電信號。這個電信號能被總線驅(qū)動程序注意到。然而。對於大多數(shù)其他總線類型。沒有任何信號能用於通知總線驅(qū)動程序。因此。Pnp管理器需要依靠其他方法來判斷設(shè)備是否消失。
功能驅(qū)動程序可以通知其管理的設(shè)備的消失(如果它知道的話),通過調(diào)用IoInvalidateDeviceState函數(shù)。然後從緊接這的IRP_MN_QUERY_PNP_DEVICE_STATE中返回PNP_DEVICE_FAILED,PNP_DEVICE_REMOVED,PNP_DEVICE_DISABLED中的任一個值,比如。如果你的ISR在讀通常結(jié)果為1和0混合的狀態(tài)端口突然得到了全部為1的值,你的驅(qū)動程序就應(yīng)該通知設(shè)備消失。更普通的情況是,總線驅(qū)動程序調(diào)用IoInvalidateDeviceRelations函數(shù)觸發(fā)一個再枚舉操作時報告某個設(shè)備枚舉失敗。另外,如果系統(tǒng)處於休眠或在其他低電源狀態(tài)時用戶拆卸了設(shè)備。那么驅(qū)動程序在接收到IRP_MN_SURRISE_REMOVAL請求前先收到了一系列電源管理IRP.這些事實說明了驅(qū)動程序應(yīng)該能應(yīng)付由設(shè)備突然消失所造成的錯誤。
3:可以使用一個DEVQUEUE來排隊和取消IRP
例如你的設(shè)備用一個單獨的隊列來管理讀寫請求。你應(yīng)該定義一個DEVQUEUE
4:pnp管理器在停止你的設(shè)備前總是先詢問。得到允許后才向你發(fā)送IRP_MN_STOP_DEVICE請求。詢問以IRP_MN_QUERY_STOP_DEVICE請求的形式出現(xiàn)。你可以回答成功或失敗。詢問的基本含義是,如果系統(tǒng)在幾納秒后向你發(fā)送IRP_MN_STOP_DEVICE。你能立即停止設(shè)備嗎?你可以用兩種稍微不同的方式處理這個詢問請求。第一種方式適合可以迅速完成或者能容易地中途結(jié)束的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管理器發(fā)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函數(shù)。如果你的設(shè)備忙。該函數(shù)返回TRUE,在這種情況下。你還需要停止隊列(檢測設(shè)備狀態(tài)和停止隊列操作需要一個自旋鎖的保護)。
即使你成功的回答了查詢。但下層驅(qū)動程序可能會失敗這個查詢。即使所有的驅(qū)動程序都成功的回答了查詢。Pnp管理器也可能決定不關(guān)閉你的設(shè)備。在這種情況下。你將收到另一個副功能嗎為IRP_MN_CANCEL_STOP_DEVICE的pnp請求。它通知設(shè)備不將被關(guān)閉。之後你應(yīng)該清除在查詢中設(shè)置的任何state值。
與pnp管理器在停止設(shè)備前向你詢問一樣。它在刪除設(shè)備前也會向你詢問。即IRP_MN_QUERY_REMOVE_DEVICE請求。你可以回答成功或失敗。與停止查詢相似。如果pnp管理器中途改變想法。它就發(fā)送IRP_MN_CANCEL_REMOVE_DEVICE請求。
防止設(shè)備過早地刪除的基本想法是在每一次開始處理請求時都獲取刪除鎖。處理完成后釋放刪除鎖。在你刪除你的設(shè)備對象前。應(yīng)確保刪除鎖未被使用。否則,你將等到這個鎖的所有引用都被釋放。
5:如果用戶使用設(shè)備管理器刪除設(shè)備。而某應(yīng)用程序正擁有該設(shè)備的打開句柄。那么操作系統(tǒng)將拒絕刪除設(shè)備并通知用戶。如果設(shè)備被用戶從計算機上物理的摘除並且沒有使用設(shè)備管理器。那么一個良好的應(yīng)用程序應(yīng)該注意WM_DEVICECHANGE消息。該消息通知應(yīng)用程序設(shè)備已經(jīng)被卸載。應(yīng)用程序接著應(yīng)該關(guān)閉設(shè)備句柄。驅(qū)動程序請求。直到句柄被真正的關(guān)閉。其實這也是刪除鎖邏輯允許你做的。
6:設(shè)備用途通知
磁盤驅(qū)動程序(以及磁盤控制器驅(qū)動程序)有時候需要了解外來的關(guān)於它們?nèi)绾伪徊僮飨到y(tǒng)使用的信息。IRP_MN_DEVICE_NOTIFICATION請求提供了獲取這些信息的手段。在IRP堆棧單元的Parameters.UsageNotification子結(jié)構(gòu)中包含了這樣兩個參數(shù),(InPath如果設(shè)備處於Type指定的路徑中,則為TRUE,否則為FALSE,TYPE用法類型)在通知請求的子派遣例程中。你應(yīng)該用一個Switch語句來區(qū)分各種通知。在大多數(shù)情況下。你將把該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);
}
僅當(dāng)你明確的認(rèn)為該通知是送往總線驅(qū)動程序的信號時才設(shè)置其Status域為STATUS_SUCCESS。如果你沒有設(shè)置STATUS_SUCCESS.則總線驅(qū)動程序?qū)⒓僭O(shè)你并不知道。因此也不處理該通知。你應(yīng)該知道你的設(shè)備不能支持某種用途。例如。假設(shè)你的磁盤設(shè)備不能用來存儲休眠文件。但如果該IRP指定InPath值。你應(yīng)該使該IRP失敗。
-----
Case DeviceUsageTypeHibernation:
??? If(inPath)
???? Return CompleteRequest(Irp,STATUS_UNSUCCESSFUL,0);
DeviceUsageTypePaging如果該通知為Inpath為TRUE則指出將有一個內(nèi)存交換文件在這個設(shè)備上打開。如果為FALSE則指出有一個內(nèi)存交換文件已被關(guān)閉。
DeviceUsageTypeDumpFile如果該通知的InPath為TRUE則指出設(shè)備已被選定用於保存系統(tǒng)崩潰時的DUMP文件。如果為False則取消這個設(shè)定。
DeviceUsageTypeHibernation如果該通知的InPath為TRUE則指出設(shè)備被選定用於保存休眠文件。如果為FALSE則取消這個選定。
7:windows2000提供了一個方法來通知用戶模式和內(nèi)核模式部件剛發(fā)生的PNP事件。Windows95有一個WM_DEVICECHANGE消息。用戶模式可以通過處理該消息來監(jiān)視。或控制系統(tǒng)中的硬件和電源配置的改變。新操作系統(tǒng)中的WM_DEVICECHANGE消息還允許用戶模式程序容易地檢測到某驅(qū)動程序允許或禁止寄存的設(shè)備接口。內(nèi)核模式也可以注冊類似的通知。參見SDK中關(guān)於WM_DEVICECHANGE,RegisterDeviceNotification,UnRegisterDeviceNotification的文檔。
轉(zhuǎn)載于:https://www.cnblogs.com/lzjsky/archive/2010/11/25/1887961.html
總結(jié)
- 上一篇: 专家答疑:在ERP系统中确保销售订单准确
- 下一篇: Illegal group refere