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