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