作者:jiangtao 時(shí)間:2002-01-04 11:58 出處:互聯(lián)網(wǎng) 責(zé)編:chinaitpower
摘要:USB開發(fā)步驟之軟件篇
我這里重點(diǎn)的介紹如何寫驅(qū)動程序,對于一些應(yīng)用程序我就不做介紹了,因?yàn)槲覍τ谀切└邔拥臇|西寫得很少。倘若再講,有班門弄斧之嫌,呵呵!
作為WIN98和WIN2K推薦的一項(xiàng)新技術(shù)來說,USB的驅(qū)動程序和以往的直接跟硬件打交道的WIN95的VXD的方式的驅(qū)動程序不同,它應(yīng)該是WDM類型的。
USB的WDM接口框圖如下(這個圖可以說是USB軟件總體框圖)
對于HID的設(shè)備,就可以采用上圖左上邊的結(jié)構(gòu),其它類的話采用右上的結(jié)構(gòu),其實(shí)右邊的結(jié)構(gòu)可以又細(xì)分成兩層,一層是Class Driver,一層是Miniport Driver。而倒數(shù)第三行的UHCD和OpenHCI分別是由INTEL和COMPAQ兩位老大定的一個和硬件有關(guān)的底層驅(qū)動程序標(biāo)準(zhǔn),各位可以根據(jù)所需要的選擇。
對于USB的驅(qū)動程序,大家還得去了解WDM驅(qū)動程序的寫法,或者早些時(shí)候的NT驅(qū)動程序,其實(shí)WDM驅(qū)動程序可以看做是NT驅(qū)動程序的一個update,只是增加了一些新的特性。
“寫驅(qū)動程序是一個很漫長和繁瑣的工作,在此之前,你最好要熟悉硬件,熟悉C/C++,還要用過DDK,會用一些調(diào)試程序,如SOFTICE和WINDBG之類。如果一切就緒,你就可以開始寫驅(qū)動程序,工作的進(jìn)程有時(shí)侯會取決于你的運(yùn)氣”。(這是一位留美的朋友對我說的,我寫出來和大家共享)
下面是我從一個朋友那里得到的一篇文章的摘要:
NT驅(qū)動程序的分層結(jié)構(gòu)
驅(qū)動程序是指管理某個外圍設(shè)備的一段程序代碼。NT采用更靈活的分層驅(qū)動方法,允許雜應(yīng)用程序和硬件之間 存在幾個驅(qū)動程序?qū)哟巍7謱訖C(jī)制允許NT更加廣泛地定義驅(qū)動程序,包括文件系統(tǒng)、邏輯卷管理器和各種網(wǎng)絡(luò)組件,各種物理設(shè)備驅(qū)動程序等等。
1、 設(shè)備驅(qū)動程序
這些是管理實(shí)際數(shù)據(jù)傳輸和控制特定類型的物理設(shè)備的操作的驅(qū)動程序,包括開始和完成I/O操作,處理中斷和執(zhí)行特定的設(shè)備要求的任何差錯處理。
2、 中間驅(qū)動程序
NT允許在物理設(shè)備驅(qū)動程序上分層任意數(shù)目的中間驅(qū)動程序。這些中間層次提供擴(kuò)展I/O系統(tǒng)的功能一種方法,而不必修改底層的驅(qū)動程序。這也是微軟鼓吹的他們的系統(tǒng)靈活的一面!實(shí)際上我覺得這樣反而犧牲了一些效率上的東西。
3、 文件系統(tǒng)驅(qū)動程序(FSD)
FSD是一類比較特殊的驅(qū)動程序,通常負(fù)責(zé)維護(hù)各種文件系統(tǒng) 所需要的磁盤結(jié)構(gòu)。注意我們并不能使用DDK來開發(fā)FSD,而必須使用Microsoft的文件系統(tǒng)開發(fā)人員工具包。
一 般比較少寫中間過濾驅(qū)動程序,過濾驅(qū)動程序它截獲和修改高層發(fā)送給類驅(qū)動程序的請求。這樣就允許利用現(xiàn)有類驅(qū)動程序的功能,而不必從頭開始寫所有程序。 NT內(nèi)核模式對象在我們的實(shí)際開發(fā)過程中的對象是設(shè)備,由于端口驅(qū)動程序已經(jīng)隱藏了硬件控制操作,因此我在這里不講述跟硬件相關(guān)的部份。如果今后的開發(fā)對 象不同,需要對硬件進(jìn)行操作的時(shí)候,可能會對中斷、DMA等有比較詳細(xì)的了解,這些內(nèi)容可以參考DDK幫助。
NT使用對象技術(shù)管理所有的數(shù)據(jù),下面分別對一般驅(qū)動程序所涉及的一些對象做一介紹。不過在介紹這些對象之前,有必要先對驅(qū)動程序的結(jié)構(gòu)做一介紹。
驅(qū)動程序結(jié)構(gòu)
NT驅(qū)動程序和一般的DOS/Windows C語言程序不一樣,它沒有main()或者WinMain()函數(shù)入口。和DLL類似地,它向操作系統(tǒng)顯露一個名稱為DriverEntry()的函數(shù), 在啟動驅(qū)動程序的時(shí)候,操作系統(tǒng)將調(diào)用這個入口。DriverEntry除了做一些必 要的設(shè)備初始化工作外,還初始化一些Dispatch例程入口。我們知道,NT應(yīng)用和設(shè)備驅(qū)動程序打交道主要是通過CreateFile、 ReadFile、WriteFile 和DeviceIoControl等Win32 API來進(jìn)行 的。這些API其實(shí)都對應(yīng)著驅(qū)動程序的一些Dispatch例程。而驅(qū)動程序除了DriverEntry以外,主要就是由這些Dispatch例 程組成的。例如調(diào)用Win32 API CreateFile的時(shí)候,操作系統(tǒng)最終轉(zhuǎn)化為對驅(qū)動程序IRP_MJ_CREATE功能代碼所對應(yīng)的 Dispatch例程的調(diào)用,如果驅(qū)動程序沒有提供該例程, CreateFile調(diào)用就會失敗。
NT中一些常用的功能代碼和Win32 API的對象關(guān)系如下所示。
功能代碼 說明
IRP_MJ_CREATE 打開設(shè)備CreateFile
IRP_MJ_CLEANUP 在關(guān)閉設(shè)備時(shí),取消掛起的I/O請求CloseHandle
IRP_MJ_CLOSE 關(guān)閉設(shè)備CloseHandle
IRP_MJ_READ 從設(shè)備獲得數(shù)據(jù)ReadFile
IRP_MJ_WRITE 向設(shè)備發(fā)送數(shù)據(jù)WriteFile
IRP_MJ_DEVICE_CONTROL 對用戶模式或內(nèi)核模式客戶程序可用的控制操作DeviceIoControl
IRP_MJ_INTERNAL_DEVICE_CONTROL 只對內(nèi)核模式客戶程序可用的控制操作
IRP_MJ_QUERY_INFORMATION 得到文件的長度GetFileLength
IRP_MJ_SET_INFORMATION 設(shè)置文件的長度SetFileLength
IRP_MJ_FLUSH_BUFFERS 寫輸出緩沖區(qū)或丟棄輸入緩沖區(qū)
FlushFileBuffers
FlushConsoleInputBuffer
PurgeComm
IRP_MJ_SHUTDOWN 系統(tǒng)關(guān)閉InitialSystemShutdown
和上面的驅(qū)動程序支持的功能代碼相對應(yīng),一般的驅(qū)動程序看起來就象下面的樣子。
DriverEntry(…) // 驅(qū)動程序入口
{
…
DeviceObject->MajorFunction[IRP_MJ_CREATE] = XXDriverCreateClose; //XX對應(yīng)的是你自己給你的驅(qū)動程序的命名
DeviceObject->MajorFunction[IRP_MJ_CLOSE] = XXDriverCreateClose;
DeviceObject->MajorFunction[IRP_MJ_READ] = XXDriverReadWrite;
DeviceObject->MajorFunction[IRP_MJ_WRITE] = XXDriverReadWrite;
…
}
XXDriverCreateClose(…) // 對應(yīng)IRP_MJ_CREATE和IRP_MJ_CLOSE的例程
{
//……….
}
XXDriverDeviceControl(…)// 對應(yīng)IRP_MJ_DEVICE_CONTROL的例程
{
//……….
}
XXDriverReadWrite(…) // 對應(yīng)IRP_MJ_READ和IRP_MJ_WRITE的例程
{
//……….
}
一 個驅(qū)動程序并不需要支持所有的功能代碼,比如如果一個驅(qū)動程序根本就不必要與用戶模式客戶程序交互,那么就不用支持IRP_MJ_CREATE和 IRP_MJ_CLOSE。又如設(shè)備不支持設(shè)備讀寫,就不用支持IRP_MJ_READ和IRP_MJ_WRITE。 驅(qū)動程序?qū)ο笫窃诓僮飨到y(tǒng)啟動驅(qū)動程序、在調(diào)用驅(qū)動程序 入口DriverEntry之前就已經(jīng)創(chuàng)建好了的,并且作為DriverEntry 函數(shù)的參數(shù)傳遞給驅(qū)動程序。如果驅(qū)動程序啟動失敗,操作 系統(tǒng)將刪除該對象。該對象的數(shù)據(jù)結(jié)構(gòu)如下。注意下表并不是完整地列出了ntddk.h中的DEVICE_OBJECT結(jié)構(gòu)體的所有數(shù) 據(jù)項(xiàng),這里僅列出了一般驅(qū)動程序可能使用到的數(shù)據(jù)項(xiàng)。
Driver對象數(shù)據(jù)項(xiàng) 說明
PDEVICE_OBJECT DeviceObject 由本驅(qū)動程序創(chuàng)建的Device對象的鏈表
ULONG Flags PDRIVER_INITIALIZE DriverInit 驅(qū)動程序初始化例程(一般較少用)
PDRIVER_STARTIO DriverStartIo StartIo例程入口,一般該例程對低層設(shè)備驅(qū)動程序用得較多, 高層驅(qū)動程序較少使用本例程。
PDRIVER_UNLOAD DriverUnload 卸載驅(qū)動程序例程,如果想在控制面版的設(shè)備里停止該設(shè)備,應(yīng)該提供本例程。
PDRIVER_DISPATCH MajorFunction[IRP_MJ_MAXIMUM_FUNCTION + 1] 驅(qū)動程序的Dispatch例程表
在上面提到過驅(qū)動程序是管理同類型的所有設(shè)備,所以上面的 結(jié)構(gòu)中DeviceObject指向的就不是單個的設(shè)備對象,而是一個對象鏈表,這個鏈表的維護(hù)在下面介紹Device對象時(shí)可以看到。 Device對象與Device Extension 驅(qū)動程序在調(diào)用IoCreateDevice函數(shù)成功后就創(chuàng)建了一個Device 對象。下面對Device對象幾個比較重要的數(shù)據(jù)做一介紹。
Device對象數(shù)據(jù)項(xiàng) 說明
PVOID DeviceExtension 指向Device Extension結(jié)構(gòu)的指針
PDRIVER_OBJECT DriverObject 指向這個設(shè)備的Driver對象的指針,IoCreateDevice會 自動填寫本數(shù)據(jù)。
ULONG Flags 指定這個設(shè)備的緩沖策略
PDEVICE_OBJECT NextDevice 指向?qū)儆谶@個驅(qū)動程序的下一個設(shè)備對象,依靠本數(shù)據(jù)來維護(hù)設(shè)備對象鏈表
CCHAR StackSize 發(fā)送到這個設(shè)備的IRP需要的I/O堆棧單元的最小數(shù)目,一般對分層驅(qū)動程序來說,本數(shù)據(jù)應(yīng)該比其下層設(shè)備的大1
ULONG AlignmentRequirement 緩沖區(qū)要求的內(nèi)存對齊,一般對分層驅(qū)動程序來說,本值應(yīng)該 和其下層設(shè)備的對齊一致
Device記錄著設(shè)備的特徵和狀態(tài)信息,對系統(tǒng)上的每個虛擬的、邏輯的和物理的設(shè)備都有一個Device對象。例如對一個硬盤驅(qū)動程序,對一個物 理硬盤有一個名稱為Partition0的Device對象,對應(yīng)整個物理磁盤,同時(shí)對硬盤的每個分區(qū),也都有一個Device對象,它們的名稱分別為 PartitionX(X從1開始,每個分區(qū)對應(yīng)一個數(shù)字)。
Device Extension是連接到Device對象的一個很重要的數(shù)據(jù)結(jié)構(gòu),它的數(shù)據(jù)結(jié)構(gòu)是由驅(qū)動程序設(shè)計(jì)者自己來確定的,在 調(diào)用IoCreateDevice的時(shí)候應(yīng)該指定它的大小,Device Extension其實(shí)是由操作系統(tǒng)在非份頁內(nèi)存池中為每個Device 對象分配的一塊內(nèi)存。由于驅(qū)動程序必須是完全可重入的, 因此使用任何全局變量和靜態(tài)變量都不是好的辦法,一般來 說和設(shè)備有關(guān)的任何需要保持的信息都應(yīng)該放到Device Extension里去。
設(shè)備的緩沖策略也必須提一下,這里的Flag的緩沖策略 主要 決定設(shè)備讀寫(功能代碼IRP_MJ_READ和IRP_MJ_WRITE)時(shí)候的 緩沖策略,另外功能代碼IRP_MJ_DEVICE_CONTROL時(shí)候的緩沖 策略是由IOCTL控制代碼本身來決定的。兩者不能混為一談。 在下面我將專門用一節(jié)來討論I/O的緩沖策略。
I/O請求包(IRP)
在上面的結(jié)構(gòu)里面已經(jīng)出現(xiàn)了IRP了,在這里對它做一說明。 在NT中,幾乎所有的I/O都是包驅(qū)動的,可以說驅(qū)動程序和操作系統(tǒng)其他部份都是通過I/O請求包來進(jìn)行交互的。我們 來看看一個I/O請求的執(zhí)行過程。
(1) 操作系統(tǒng)的I/O管理器從非分頁內(nèi)存分配一個IRP,響應(yīng)一個I/O請求?;谟煽蛻糁付ǖ腎/O函數(shù),I/O管理器將該 IRP傳遞給合適的驅(qū)動程序的Dispatch例程。
(2) Dispatch例程檢查請求的參數(shù)是否有效,如果有效,驅(qū)動程序根據(jù)請求的內(nèi)容進(jìn)行一系列的操作。否則設(shè)置錯 誤狀態(tài)信息直接返回。
(3) 操作完成時(shí),將數(shù)據(jù)(如果有)和狀態(tài)信息存放到IRP中 并返回給I/O管理器。
(4) I/O管理器對返回的IRP進(jìn)行適當(dāng)?shù)奶幚砗髮⒆詈鬆顟B(tài)和 數(shù)據(jù)(如果有)返回給用戶。
一個IRP的主要數(shù)據(jù)項(xiàng)如下表所示。
IRP包括一個IRP頭和一個IRP stack 的區(qū)域。由于WDM的模式下都是包驅(qū)動的,所里IRP可以說是一個非常重要的東東。還有那個該死的URB(God damn URB!)[人一輩子真的很過癮,有些人或有些事你明明不喜歡或做不來,可是有時(shí)侯你又不得不硬著頭皮去做,就像一大堆球迷圍著一堆狗屎般的中國足球,那班傻兒真是要錢不要臉,丟咱中國人的臉]
IRP主要數(shù)據(jù)項(xiàng) 說明
IO_STATUS_BLOCK IoStatus 存放I/O請求的狀態(tài)
PVOID AssociatedIrp.SystemBuffer 如果設(shè)備執(zhí)行緩沖I/O,則為指向系統(tǒng)空間緩沖區(qū)的指針。 否則為NULL
PMDL MdlAddress 如果設(shè)備執(zhí)行直接I/O,指向用戶空間緩沖區(qū)的內(nèi)存描述表的指針
PVOID UserBuffer I/O緩沖區(qū)的用戶空間地址
BOOLEAN Cancel 指示IRP已被取消
關(guān)于AssociatedIrp.SystemBuffer、MdlAddress和UserBuffer將在 下面的I/O緩沖區(qū)策略里面更詳細(xì)地討論。
NT還有更多其他的對象,例如中斷對象、Controller對象、定時(shí)器對象等等,但在我們開發(fā)的驅(qū)動程序中并沒有用到,因此在這里不做介紹。
I/O緩沖策略
很明顯的,驅(qū)動程序和客戶應(yīng)用程序經(jīng)常需要進(jìn)行數(shù)據(jù)交換,但我們知道驅(qū)動程序和客戶應(yīng)用程序可能不在同一個地址空間,因此操作系統(tǒng)必須解決兩者之間的數(shù)據(jù)交換。這就就設(shè)計(jì)到設(shè)備的I/O緩沖策略。
讀寫請求的I/O緩沖策略
前面說到通過設(shè)置Device對象的Flag可以選擇控制處理讀寫請求的I/O緩沖策略。下面對這些緩沖策略分別做一介紹。
1、緩沖I/O(DO_BUFFERED_IO)
在讀寫請求的一開始,I/O管理器檢查用戶緩沖區(qū)的可訪問性,然后分配與調(diào)用者的緩沖區(qū)一樣大的非分頁池,并把它的地址放在IRP的AssociatedIrp.SystemBuffer域中。驅(qū)動程序就利用這個域來進(jìn)行實(shí)際數(shù)據(jù)的傳輸。
對 于IRP_MJ_READ讀請求,I/O管理器還把IRP的UserBuffer域設(shè)置 成調(diào)用者緩沖區(qū)的用戶空間地址。當(dāng)請求完成時(shí),I/O管理器利用 這個地址將數(shù)據(jù)從驅(qū)動程序的系統(tǒng)空間拷貝回調(diào)用者的緩沖區(qū)。對 于IRP_MJ_WRITE寫請求,UserBuffer被設(shè)置為NULL,并把用戶緩沖 區(qū)的數(shù)據(jù)拷貝到系統(tǒng)緩沖區(qū)中。
2、 直接I/O(DO_DIRECT_IO)
I/O 管理器首先檢查用戶緩沖區(qū)的可訪問性,并在物理內(nèi)存中鎖定它。然后它為該緩沖區(qū)創(chuàng)建一個內(nèi)存描述表(MDL),并把MDL的地址 存放在IRP的MdlAddress域中。AssociatedIrp.SystemBuffer和 UserBuffer都被設(shè)置為NULL。驅(qū)動程序可以調(diào)用函數(shù) MmGetSystemAddressForMdl得到用戶緩沖區(qū)的系統(tǒng)空間地址,從而 進(jìn)行數(shù)據(jù)操作。這個函數(shù)將調(diào)用者的緩沖區(qū)映射到非份頁的地址空 間。驅(qū)動程序完成I/O請求后,系統(tǒng)自動從系統(tǒng)空間解除緩沖區(qū)的映射。
3、 這兩種方法都不是
這種情況比較少用,因?yàn)檫@需要驅(qū)動程序自己來處理緩沖問題。 I/O管理器僅把調(diào)用者緩沖區(qū)的用戶空間地址放到IRP的UserBuffer 域中。我們并不推薦這種方式。
IOCTL緩沖區(qū)的緩沖策略
IOCTL請求涉及來自調(diào)用者的輸入緩沖區(qū)和返回到調(diào)用者的輸出 緩沖區(qū)。為了理解IOCTL請求,我們先來看看WIN32 API DeviceIoControl函數(shù)的原型。
BOOL DeviceIoControl (
HANDLE hDevice, // 設(shè)備句柄
DWORD dwIoControlCode, // IOCTL請求操作代碼
LPVOID lpInBuffer, // 輸入緩沖區(qū)地址
DWORD nInBufferSize, // 輸入緩沖區(qū)大小
LPVOID lpOutBuffer, // 輸出緩沖區(qū)地址
DWORD nOutBufferSize, // 輸出緩沖區(qū)大小
LPDWORD lpBytesReturned, // 存放返回字節(jié)數(shù)的指針
LPOVERLAPPED lpOverlapped // 用于同步操作的Overlapped結(jié)構(gòu)體指針
);
IOCTL請求有四種緩沖策略,下面一一介紹。
1、 輸入輸出緩沖I/O(METHOD_BUFFERED)
I/O 管理器首先分配一個非分頁池,它足夠大地存放調(diào)用者的輸入或輸出緩沖區(qū)(不管哪個更大)。非分頁緩沖區(qū)的地址放在IRP的 AssociatedIrp.SystemBuffer域中,然后把IOCTL的輸入數(shù)據(jù)拷貝 到這個非份頁緩沖區(qū)中,并把IRP的UserBuffer域設(shè)置成調(diào)用者輸出緩沖區(qū)的用戶空間地址。當(dāng)驅(qū)動程序完成IOCTL請求時(shí),I/O管理器將這個 非份頁緩沖區(qū)中的數(shù)據(jù)拷貝到調(diào)用者的輸出緩沖區(qū)。注意這里同一個非份頁池同時(shí)用于輸入和輸出緩沖區(qū),因此驅(qū)動程序在向緩沖區(qū)寫東西之前應(yīng)該把輸入的所有數(shù) 據(jù)讀出來。
2、 直接輸入緩沖輸出I/O(METHOD_IN_DIRECT)
I/O管理器首先檢查調(diào)用者輸入緩沖區(qū)的可訪問性, 并在物理內(nèi)存中將其鎖定。然后為該輸入緩沖區(qū)創(chuàng)建一個MDL,并把指定該MDL的指針存放到IRP的MdlAddress域中。同時(shí),I/O管理器還在非 份頁池中分配一輸出緩沖區(qū),并把這個緩沖區(qū)的地址存放在IRP的AssociatedIrp.SystemBuffer域中,并把IRP的 UserBuffer域設(shè)置成調(diào)用者輸出緩沖區(qū)的用戶空間地址。當(dāng)驅(qū)動程序完成IOCTL請求時(shí),I/O管理器將非份頁緩沖區(qū)中的數(shù)據(jù)拷貝到調(diào)用者的輸出 緩沖區(qū)。
3、 緩沖輸入直接輸出I/O(METHOD_OUT_DIRECT)
I/O管理器首先檢查調(diào)用者輸出緩沖區(qū)的可訪問性,并在 物理內(nèi)存中將其鎖定。然后為該輸出緩沖區(qū)創(chuàng)建一個MDL,并把指定該MDL的指針存放到IRP的MdlAddress域中。同時(shí),I/O管理器還在非份頁 池中分配一輸入緩沖區(qū),并把這個緩沖區(qū)的地址存放在IRP的AssociatedIrp.SystemBuffer域中, 同時(shí)把調(diào)用者用戶輸入緩沖區(qū)中的數(shù)據(jù)拷貝到系統(tǒng)緩沖區(qū)中,并把IRP的 UserBuffer域設(shè)置為NULL。
4、 上面三種方法都不是(METHOD_NEITHER)
I/O管理器把調(diào)用者的輸入緩沖區(qū)的地址放到IRP當(dāng)前I/O堆棧單元的Parameters.Devi ceIoControl.TypeInputBuffer域中,把輸出緩沖 區(qū)的地址存放到IRP的UserBuffer域中。這兩個地址都是用戶空間地 址。
從 上面的說明可以看出,在執(zhí)行緩沖I/O時(shí),I/O管理器將在非份頁池 中分配內(nèi)存,如果調(diào)用者的緩沖區(qū)比較大時(shí),分配的非份頁池也將 比較大。非份頁池是系統(tǒng)比較寶貴的資源,因此,如果調(diào)用者的緩 沖區(qū)比較大時(shí),我們一般采用直接I/O的方式(例如磁盤讀寫請求等), 這樣不僅節(jié)省系統(tǒng)資源,另一方面由于省去了I/O管理器在系統(tǒng)緩沖 區(qū)和調(diào)用者緩沖區(qū)之間的數(shù)據(jù)拷貝,也提高了效率,這對存在大量 數(shù)據(jù)傳送的驅(qū)動程序尤其明顯。
可以注意到DDK中的Samples下,幾乎所有的例程的讀寫請求都是直 接I/O的,而對于IOCTL請求則是緩沖區(qū)I/O的居多。
開始驅(qū)動程序設(shè)計(jì)
下面的文字是從Microsoft的DDK幫助中節(jié)選出來的,它讓我們明 白在開始設(shè)計(jì)驅(qū)動程序應(yīng)該注意些什么問題,這些都是具有普遍 意義的開發(fā)準(zhǔn)則。應(yīng)該支持哪些I/O請求在開始寫任何代碼之前, 應(yīng)該首先確定我們的驅(qū)動程序應(yīng)該處理哪些IRP例程。
如果你在設(shè)計(jì)一個設(shè)備驅(qū)動程序,你應(yīng)該支持和其他相同類型 設(shè)備的NT驅(qū)動程序相同的IRP_MJ_XXX和IOCTL請求代碼。
如 果你是在設(shè)計(jì)一個中間層NT驅(qū)動程序,應(yīng)該首先確認(rèn)你下層 驅(qū)動程序所管理的設(shè)備,因?yàn)橐粋€高層的驅(qū)動程序必須具有低層 驅(qū)動程序絕大多數(shù)IRP_MJ_XXX例程入口。高層驅(qū)動程序在接到I/O 請求時(shí),在確定自身IRP當(dāng)前堆棧單元參數(shù)有效的前提下 ,設(shè)置好IRP中下一個低層驅(qū)動程序的堆棧單元,然后再調(diào)用IoCallDriver 將請求傳遞給下層驅(qū)動程序處理。
一旦決定好了你的驅(qū)動 程序應(yīng)該處理哪些IRP_MJ_XXX,就可以開始 確定驅(qū)動程序應(yīng)該有多少個Dispatch例程。當(dāng)然也可以考慮把某些 RP_MJ_XXX處理的例程合并為同一例程處理。例如在ChangerDisk和 VDisk里,對IRP_MJ_CREATE和IRP_MJ_CLOSE處理的例程就是同一函數(shù)。 對IRP_MJ_READ和IRP_MJ_WRITE處理的例程也是同一個函數(shù)。
應(yīng)該有多少個Device對象?
一個驅(qū)動程序必須 為它所管理的每個可能成為I/O請求的目標(biāo)的物理和邏輯設(shè)備創(chuàng)建一個命名Device對象。一些低層的驅(qū)動程序還可能要創(chuàng)建一些不確定數(shù)目的Device 對象。例如一個硬盤驅(qū)動程序必須為每一個物理硬盤創(chuàng)建一個Device對象,同時(shí)還必須為每個物理磁盤上的每個邏輯分區(qū)創(chuàng)建一個Device對象。
一 個高層驅(qū)動驅(qū)動程序必須為它所代表的虛擬設(shè)備創(chuàng)建一個Device 對象,這樣更高層的驅(qū)動程序才能連接它們的Device對象到這個驅(qū)動程序的Device對象。另外,一個高層驅(qū)動程序通常為它低層驅(qū)動 程序所創(chuàng)建的Device對象創(chuàng)建一系列的虛擬或邏輯Device對象。
盡管你可以分階段來設(shè)計(jì)你的驅(qū)動程序,因此一個處在開發(fā)階段的 驅(qū)動程序不必一開始就創(chuàng)建出所有它將要處理的所有Device對象。 但從一開始就確定好你最終要創(chuàng)建的所有Device對象將有助于設(shè)計(jì)者所要解決的任何同步問題。另外,確定所要創(chuàng)建的Device對象還有助于你定義 Device對象的Device Extension的內(nèi)容和數(shù)據(jù)結(jié)構(gòu)。
開始驅(qū)動程序開發(fā)
驅(qū)動程序的開發(fā)是一個從粗到細(xì)逐步求精的 過程。NT DDK的src 目錄下有一個龐大的樣板代碼,幾乎覆蓋了所有類型的設(shè)備驅(qū)動程序、高層驅(qū)動程序和過濾器驅(qū)動程序。在開始開發(fā)你的驅(qū)動程序之前,你應(yīng)該在這個樣板庫下面尋 找是否有和你所要開發(fā)的類似類型的例程。例如我們所開發(fā)的驅(qū)動程序,雖然DDK對USB描述得不是很詳細(xì),我們還是可以在src\storage class目錄發(fā)現(xiàn)很多和USB設(shè)備有關(guān)的驅(qū)動程序。下面我們來看開發(fā)驅(qū)動程序的基本步驟。
最簡的驅(qū)動程序框架
1、 寫一個DriverEntry例程,在里面調(diào)用IoCreateDevice創(chuàng)建 一個Device對象。
2、 寫一個處理IRP_MJ_CREATE請求的Dispatch例程的基本框架 (參見DDK Kernel-Mode Drivers 4.4.3描述的一個DispatchCreate 例程所要完成的最基本工作。當(dāng)然寫了DispatchCreate例程后, 要在DriverEntry例程為IRP_MJ_CREATE初始化例程入口)。如果驅(qū)動程序創(chuàng)建了多于一個Device對象,則必須為 IRP_MJ_CLOSE 請求寫一個例程,該例程通常情況下可以和DispatchCreate共用一個例程,參見參見DDK Kernel-Mode Drivers 4.4.3。
3、 編譯連接你的驅(qū)動程序。
用下面的方法來測試你的驅(qū)動程序。
首先按上面介紹的方法安裝好驅(qū)動程序。
其 次我們還得為NT邏輯設(shè)備名稱和目標(biāo)Device對象名稱之間建立 起符號連接,我們在前面已經(jīng)知道Device對象名稱對WIN32用戶模式 是不可見的,是不能直接通過API來訪問的,WIN 32 API只能訪問NT 邏輯設(shè)備名稱。我們可以通過修改注冊表來建立這兩種名稱之間的符 號連接。運(yùn)行REGEDT32.EXE在\HKEY_LOCAL_MACHINE\ System CurrentControlSet\Control\ Session Manager\ DOS Devices下建立起符號連接(這種符號連接也可以在驅(qū)動程序里調(diào)用函數(shù) IoCreateSymbolicLink來創(chuàng)建)。
重新啟動系統(tǒng)。
編寫一個簡單的測試程序調(diào)用WIN32API CreateFile函數(shù)以剛才你命名的NT邏輯設(shè)備名打開這個設(shè)備。如果打開成功,那么你也就成功地寫出了一個最簡單的驅(qū)動程序了。
支持更多的設(shè)備I/O請求
例 如你的驅(qū)動程序可能需要對IRP_MJ_READ請求做出響應(yīng)(完成后可用WIN32 API ReadFile函數(shù)進(jìn)行測試)。如果你的驅(qū)動程序需要能夠手工卸載,那么還必須對IRP_MJ_CLOSE做出響應(yīng)。為你所需要處理 IRP_MJ_XXX寫好處理例程,并在DriverEntry里面初始化好這些例 程入口。
一個低層的驅(qū)動程序可能需要最起碼一個StartIo,ISR和DpcForIsr 例程,可能需要一個SynchCritSection例程,如果設(shè)備使用了DMA, 那么可能還需要一個AdapterControl例程。關(guān)于這些例程,請參考 DDK相應(yīng)文檔。
對于高層驅(qū)動程序可能需要一個或多個IoCompletion例程,最起碼 完成檢查I/O狀態(tài)塊然后調(diào)用IoCompleteRequest的工作。 如果需要,還要對Device Extension數(shù)據(jù)結(jié)構(gòu)和內(nèi)容做些修改