电竞比分网-中国电竞赛事及体育赛事平台

分享

USB開(kāi)發(fā)步驟之軟件篇

 Archangel 2007-08-07
USB開(kāi)發(fā)步驟之軟件篇
作者:jiangtao 時(shí)間:2002-01-04 11:58 出處:互聯(lián)網(wǎng) 責(zé)編:chinaitpower
              摘要:USB開(kāi)發(fā)步驟之軟件篇

 


我這里重點(diǎn)的介紹如何寫(xiě)驅(qū)動(dòng)程序,對(duì)于一些應(yīng)用程序我就不做介紹了,因?yàn)槲覍?duì)于那些高層的東西寫(xiě)得很少。倘若再講,有班門(mén)弄斧之嫌,呵呵!
作為WIN98WIN2K推薦的一項(xiàng)新技術(shù)來(lái)說(shuō),USB的驅(qū)動(dòng)程序和以往的直接跟硬件打交道的WIN95VXD的方式的驅(qū)動(dòng)程序不同,它應(yīng)該是WDM類型的。

USBWDM接口框圖如下(這個(gè)圖可以說(shuō)是USB軟件總體框圖)

wdmusb1.gif (3339 bytes)

對(duì)于HID的設(shè)備,就可以采用上圖左上邊的結(jié)構(gòu),其它類的話采用右上的結(jié)構(gòu),其實(shí)右邊的結(jié)構(gòu)可以又細(xì)分成兩層,一層是Class Driver,一層是Miniport Driver。而倒數(shù)第三行的UHCDOpenHCI分別是由INTELCOMPAQ兩位老大定的一個(gè)和硬件有關(guān)的底層驅(qū)動(dòng)程序標(biāo)準(zhǔn),各位可以根據(jù)所需要的選擇。

對(duì)于USB的驅(qū)動(dòng)程序,大家還得去了解WDM驅(qū)動(dòng)程序的寫(xiě)法,或者早些時(shí)候的NT驅(qū)動(dòng)程序,其實(shí)WDM驅(qū)動(dòng)程序可以看做是NT驅(qū)動(dòng)程序的一個(gè)update,只是增加了一些新的特性。

“寫(xiě)驅(qū)動(dòng)程序是一個(gè)很漫長(zhǎng)和繁瑣的工作,在此之前,你最好要熟悉硬件,熟悉C/C++,還要用過(guò)DDK,會(huì)用一些調(diào)試程序,如SOFTICEWINDBG之類。如果一切就緒,你就可以開(kāi)始寫(xiě)驅(qū)動(dòng)程序,工作的進(jìn)程有時(shí)侯會(huì)取決于你的運(yùn)氣”。(這是一位留美的朋友對(duì)我說(shuō)的,我寫(xiě)出來(lái)和大家共享)

下面是我從一個(gè)朋友那里得到的一篇文章的摘要:


NT驅(qū)動(dòng)程序的分層結(jié)構(gòu)
驅(qū)動(dòng)程序是指管理某個(gè)外圍設(shè)備的一段程序代碼。NT采用更靈活的分層驅(qū)動(dòng)方法,允許雜應(yīng)用程序和硬件之間 存在幾個(gè)驅(qū)動(dòng)程序?qū)哟巍7謱訖C(jī)制允許NT更加廣泛地定義驅(qū)動(dòng)程序,包括文件系統(tǒng)、邏輯卷管理器和各種網(wǎng)絡(luò)組件,各種物理設(shè)備驅(qū)動(dòng)程序等等。

1、 設(shè)備驅(qū)動(dòng)程序
這些是管理實(shí)際數(shù)據(jù)傳輸和控制特定類型的物理設(shè)備的操作的驅(qū)動(dòng)程序,包括開(kāi)始和完成I/O操作,處理中斷和執(zhí)行特定的設(shè)備要求的任何差錯(cuò)處理。

2、 中間驅(qū)動(dòng)程序
NT允許在物理設(shè)備驅(qū)動(dòng)程序上分層任意數(shù)目的中間驅(qū)動(dòng)程序。這些中間層次提供擴(kuò)展I/O系統(tǒng)的功能一種方法,而不必修改底層的驅(qū)動(dòng)程序。這也是微軟鼓吹的他們的系統(tǒng)靈活的一面!實(shí)際上我覺(jué)得這樣反而犧牲了一些效率上的東西。

3、 文件系統(tǒng)驅(qū)動(dòng)程序(FSD)
FSD是一類比較特殊的驅(qū)動(dòng)程序,通常負(fù)責(zé)維護(hù)各種文件系統(tǒng) 所需要的磁盤(pán)結(jié)構(gòu)。注意我們并不能使用DDK來(lái)開(kāi)發(fā)FSD,而必須使用Microsoft的文件系統(tǒng)開(kāi)發(fā)人員工具包。

一 般比較少寫(xiě)中間過(guò)濾驅(qū)動(dòng)程序,過(guò)濾驅(qū)動(dòng)程序它截獲和修改高層發(fā)送給類驅(qū)動(dòng)程序的請(qǐng)求。這樣就允許利用現(xiàn)有類驅(qū)動(dòng)程序的功能,而不必從頭開(kāi)始寫(xiě)所有程序。 NT內(nèi)核模式對(duì)象在我們的實(shí)際開(kāi)發(fā)過(guò)程中的對(duì)象是設(shè)備,由于端口驅(qū)動(dòng)程序已經(jīng)隱藏了硬件控制操作,因此我在這里不講述跟硬件相關(guān)的部份。如果今后的開(kāi)發(fā)對(duì) 象不同,需要對(duì)硬件進(jìn)行操作的時(shí)候,可能會(huì)對(duì)中斷、DMA等有比較詳細(xì)的了解,這些內(nèi)容可以參考DDK幫助。

NT使用對(duì)象技術(shù)管理所有的數(shù)據(jù),下面分別對(duì)一般驅(qū)動(dòng)程序所涉及的一些對(duì)象做一介紹。不過(guò)在介紹這些對(duì)象之前,有必要先對(duì)驅(qū)動(dòng)程序的結(jié)構(gòu)做一介紹。

 
驅(qū)動(dòng)程序結(jié)構(gòu)
NT驅(qū)動(dòng)程序和一般的DOS/Windows C語(yǔ)言程序不一樣,它沒(méi)有main()或者WinMain()函數(shù)入口。和DLL類似地,它向操作系統(tǒng)顯露一個(gè)名稱為DriverEntry()的函數(shù), 在啟動(dòng)驅(qū)動(dòng)程序的時(shí)候,操作系統(tǒng)將調(diào)用這個(gè)入口。DriverEntry除了做一些必 要的設(shè)備初始化工作外,還初始化一些Dispatch例程入口。我們知道,NT應(yīng)用和設(shè)備驅(qū)動(dòng)程序打交道主要是通過(guò)CreateFile、 ReadFile、WriteFile 和DeviceIoControl等Win32 API來(lái)進(jìn)行 的。這些API其實(shí)都對(duì)應(yīng)著驅(qū)動(dòng)程序的一些Dispatch例程。而驅(qū)動(dòng)程序除了DriverEntry以外,主要就是由這些Dispatch例 程組成的。例如調(diào)用Win32 API CreateFile的時(shí)候,操作系統(tǒng)最終轉(zhuǎn)化為對(duì)驅(qū)動(dòng)程序IRP_MJ_CREATE功能代碼所對(duì)應(yīng)的 Dispatch例程的調(diào)用,如果驅(qū)動(dòng)程序沒(méi)有提供該例程, CreateFile調(diào)用就會(huì)失敗。
NT中一些常用的功能代碼和Win32 API的對(duì)象關(guān)系如下所示。
功能代碼                                                                  說(shuō)明
IRP_MJ_CREATE                                                 打開(kāi)設(shè)備CreateFile
IRP_MJ_CLEANUP                                              在關(guān)閉設(shè)備時(shí),取消掛起的I/O請(qǐng)求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                               對(duì)用戶模式或內(nèi)核模式客戶程序可用的控制操作DeviceIoControl
IRP_MJ_INTERNAL_DEVICE_CONTROL            只對(duì)內(nèi)核模式客戶程序可用的控制操作
IRP_MJ_QUERY_INFORMATION                           得到文件的長(zhǎng)度GetFileLength
IRP_MJ_SET_INFORMATION                                 設(shè)置文件的長(zhǎng)度SetFileLength
IRP_MJ_FLUSH_BUFFERS                                  寫(xiě)輸出緩沖區(qū)或丟棄輸入緩沖區(qū)
FlushFileBuffers
FlushConsoleInputBuffer
PurgeComm
IRP_MJ_SHUTDOWN                                           系統(tǒng)關(guān)閉InitialSystemShutdown

和上面的驅(qū)動(dòng)程序支持的功能代碼相對(duì)應(yīng),一般的驅(qū)動(dòng)程序看起來(lái)就象下面的樣子。
DriverEntry(…) // 驅(qū)動(dòng)程序入口
{

DeviceObject->MajorFunction[IRP_MJ_CREATE] = XXDriverCreateClose; //XX對(duì)應(yīng)的是你自己給你的驅(qū)動(dòng)程序的命名
DeviceObject->MajorFunction[IRP_MJ_CLOSE] = XXDriverCreateClose;
DeviceObject->MajorFunction[IRP_MJ_READ] = XXDriverReadWrite;
DeviceObject->MajorFunction[IRP_MJ_WRITE] = XXDriverReadWrite;

}
XXDriverCreateClose(…) // 對(duì)應(yīng)IRP_MJ_CREATE和IRP_MJ_CLOSE的例程
{
//……….
}
XXDriverDeviceControl(…)// 對(duì)應(yīng)IRP_MJ_DEVICE_CONTROL的例程
{
//……….
}
XXDriverReadWrite(…) // 對(duì)應(yīng)IRP_MJ_READ和IRP_MJ_WRITE的例程
{
//……….
}

一 個(gè)驅(qū)動(dòng)程序并不需要支持所有的功能代碼,比如如果一個(gè)驅(qū)動(dòng)程序根本就不必要與用戶模式客戶程序交互,那么就不用支持IRP_MJ_CREATE和 IRP_MJ_CLOSE。又如設(shè)備不支持設(shè)備讀寫(xiě),就不用支持IRP_MJ_READ和IRP_MJ_WRITE。 驅(qū)動(dòng)程序?qū)ο笫窃诓僮飨到y(tǒng)啟動(dòng)驅(qū)動(dòng)程序、在調(diào)用驅(qū)動(dòng)程序 入口DriverEntry之前就已經(jīng)創(chuàng)建好了的,并且作為DriverEntry 函數(shù)的參數(shù)傳遞給驅(qū)動(dòng)程序。如果驅(qū)動(dòng)程序啟動(dòng)失敗,操作 系統(tǒng)將刪除該對(duì)象。該對(duì)象的數(shù)據(jù)結(jié)構(gòu)如下。注意下表并不是完整地列出了ntddk.h中的DEVICE_OBJECT結(jié)構(gòu)體的所有數(shù) 據(jù)項(xiàng),這里僅列出了一般驅(qū)動(dòng)程序可能使用到的數(shù)據(jù)項(xiàng)?!?/p>

Driver對(duì)象數(shù)據(jù)項(xiàng)   說(shuō)明
PDEVICE_OBJECT  DeviceObject   由本驅(qū)動(dòng)程序創(chuàng)建的Device對(duì)象的鏈表
ULONG Flags     PDRIVER_INITIALIZE DriverInit     驅(qū)動(dòng)程序初始化例程(一般較少用)
PDRIVER_STARTIO DriverStartIo        StartIo例程入口,一般該例程對(duì)低層設(shè)備驅(qū)動(dòng)程序用得較多, 高層驅(qū)動(dòng)程序較少使用本例程。
PDRIVER_UNLOAD  DriverUnload   卸載驅(qū)動(dòng)程序例程,如果想在控制面版的設(shè)備里停止該設(shè)備,應(yīng)該提供本例程。
PDRIVER_DISPATCH MajorFunction[IRP_MJ_MAXIMUM_FUNCTION + 1]     驅(qū)動(dòng)程序的Dispatch例程表

在上面提到過(guò)驅(qū)動(dòng)程序是管理同類型的所有設(shè)備,所以上面的 結(jié)構(gòu)中DeviceObject指向的就不是單個(gè)的設(shè)備對(duì)象,而是一個(gè)對(duì)象鏈表,這個(gè)鏈表的維護(hù)在下面介紹Device對(duì)象時(shí)可以看到。 Device對(duì)象與Device Extension 驅(qū)動(dòng)程序在調(diào)用IoCreateDevice函數(shù)成功后就創(chuàng)建了一個(gè)Device 對(duì)象。下面對(duì)Device對(duì)象幾個(gè)比較重要的數(shù)據(jù)做一介紹。

Device對(duì)象數(shù)據(jù)項(xiàng)       說(shuō)明
PVOID DeviceExtension   指向Device Extension結(jié)構(gòu)的指針
PDRIVER_OBJECT DriverObject 指向這個(gè)設(shè)備的Driver對(duì)象的指針,IoCreateDevice會(huì) 自動(dòng)填寫(xiě)本數(shù)據(jù)。
ULONG Flags 指定這個(gè)設(shè)備的緩沖策略
PDEVICE_OBJECT NextDevice 指向?qū)儆谶@個(gè)驅(qū)動(dòng)程序的下一個(gè)設(shè)備對(duì)象,依靠本數(shù)據(jù)來(lái)維護(hù)設(shè)備對(duì)象鏈表
CCHAR StackSize   發(fā)送到這個(gè)設(shè)備的IRP需要的I/O堆棧單元的最小數(shù)目,一般對(duì)分層驅(qū)動(dòng)程序來(lái)說(shuō),本數(shù)據(jù)應(yīng)該比其下層設(shè)備的大1
ULONG AlignmentRequirement 緩沖區(qū)要求的內(nèi)存對(duì)齊,一般對(duì)分層驅(qū)動(dòng)程序來(lái)說(shuō),本值應(yīng)該 和其下層設(shè)備的對(duì)齊一致

Device記錄著設(shè)備的特徵和狀態(tài)信息,對(duì)系統(tǒng)上的每個(gè)虛擬的、邏輯的和物理的設(shè)備都有一個(gè)Device對(duì)象。例如對(duì)一個(gè)硬盤(pán)驅(qū)動(dòng)程序,對(duì)一個(gè)物 理硬盤(pán)有一個(gè)名稱為Partition0的Device對(duì)象,對(duì)應(yīng)整個(gè)物理磁盤(pán),同時(shí)對(duì)硬盤(pán)的每個(gè)分區(qū),也都有一個(gè)Device對(duì)象,它們的名稱分別為 PartitionX(X從1開(kāi)始,每個(gè)分區(qū)對(duì)應(yīng)一個(gè)數(shù)字)。
Device Extension是連接到Device對(duì)象的一個(gè)很重要的數(shù)據(jù)結(jié)構(gòu),它的數(shù)據(jù)結(jié)構(gòu)是由驅(qū)動(dòng)程序設(shè)計(jì)者自己來(lái)確定的,在 調(diào)用IoCreateDevice的時(shí)候應(yīng)該指定它的大小,Device Extension其實(shí)是由操作系統(tǒng)在非份頁(yè)內(nèi)存池中為每個(gè)Device 對(duì)象分配的一塊內(nèi)存。由于驅(qū)動(dòng)程序必須是完全可重入的, 因此使用任何全局變量和靜態(tài)變量都不是好的辦法,一般來(lái) 說(shuō)和設(shè)備有關(guān)的任何需要保持的信息都應(yīng)該放到Device Extension里去。
設(shè)備的緩沖策略也必須提一下,這里的Flag的緩沖策略 主要 決定設(shè)備讀寫(xiě)(功能代碼IRP_MJ_READ和IRP_MJ_WRITE)時(shí)候的 緩沖策略,另外功能代碼IRP_MJ_DEVICE_CONTROL時(shí)候的緩沖 策略是由IOCTL控制代碼本身來(lái)決定的。兩者不能混為一談。 在下面我將專門(mén)用一節(jié)來(lái)討論I/O的緩沖策略。
I/O請(qǐng)求包(IRP)
在上面的結(jié)構(gòu)里面已經(jīng)出現(xiàn)了IRP了,在這里對(duì)它做一說(shuō)明。 在NT中,幾乎所有的I/O都是包驅(qū)動(dòng)的,可以說(shuō)驅(qū)動(dòng)程序和操作系統(tǒng)其他部份都是通過(guò)I/O請(qǐng)求包來(lái)進(jìn)行交互的。我們 來(lái)看看一個(gè)I/O請(qǐng)求的執(zhí)行過(guò)程。
(1) 操作系統(tǒng)的I/O管理器從非分頁(yè)內(nèi)存分配一個(gè)IRP,響應(yīng)一個(gè)I/O請(qǐng)求?;谟煽蛻糁付ǖ腎/O函數(shù),I/O管理器將該 IRP傳遞給合適的驅(qū)動(dòng)程序的Dispatch例程。
(2) Dispatch例程檢查請(qǐng)求的參數(shù)是否有效,如果有效,驅(qū)動(dòng)程序根據(jù)請(qǐng)求的內(nèi)容進(jìn)行一系列的操作。否則設(shè)置錯(cuò) 誤狀態(tài)信息直接返回。
(3) 操作完成時(shí),將數(shù)據(jù)(如果有)和狀態(tài)信息存放到IRP中 并返回給I/O管理器。
(4) I/O管理器對(duì)返回的IRP進(jìn)行適當(dāng)?shù)奶幚砗髮⒆詈鬆顟B(tài)和 數(shù)據(jù)(如果有)返回給用戶。

一個(gè)IRP的主要數(shù)據(jù)項(xiàng)如下表所示。
IRP包括一個(gè)IRP頭和一個(gè)IRP stack 的區(qū)域。由于WDM的模式下都是包驅(qū)動(dòng)的,所里IRP可以說(shuō)是一個(gè)非常重要的東東。還有那個(gè)該死的URB(God damn URB!)[人一輩子真的很過(guò)癮,有些人或有些事你明明不喜歡或做不來(lái),可是有時(shí)侯你又不得不硬著頭皮去做,就像一大堆球迷圍著一堆狗屎般的中國(guó)足球,那班傻兒真是要錢(qián)不要臉,丟咱中國(guó)人的臉]

IRP主要數(shù)據(jù)項(xiàng) 說(shuō)明
IO_STATUS_BLOCK IoStatus 存放I/O請(qǐng)求的狀態(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還有更多其他的對(duì)象,例如中斷對(duì)象、Controller對(duì)象、定時(shí)器對(duì)象等等,但在我們開(kāi)發(fā)的驅(qū)動(dòng)程序中并沒(méi)有用到,因此在這里不做介紹。
I/O緩沖策略
很明顯的,驅(qū)動(dòng)程序和客戶應(yīng)用程序經(jīng)常需要進(jìn)行數(shù)據(jù)交換,但我們知道驅(qū)動(dòng)程序和客戶應(yīng)用程序可能不在同一個(gè)地址空間,因此操作系統(tǒng)必須解決兩者之間的數(shù)據(jù)交換。這就就設(shè)計(jì)到設(shè)備的I/O緩沖策略。
讀寫(xiě)請(qǐng)求的I/O緩沖策略
前面說(shuō)到通過(guò)設(shè)置Device對(duì)象的Flag可以選擇控制處理讀寫(xiě)請(qǐng)求的I/O緩沖策略。下面對(duì)這些緩沖策略分別做一介紹。
1、緩沖I/O(DO_BUFFERED_IO)
在讀寫(xiě)請(qǐng)求的一開(kāi)始,I/O管理器檢查用戶緩沖區(qū)的可訪問(wèn)性,然后分配與調(diào)用者的緩沖區(qū)一樣大的非分頁(yè)池,并把它的地址放在IRP的AssociatedIrp.SystemBuffer域中。驅(qū)動(dòng)程序就利用這個(gè)域來(lái)進(jìn)行實(shí)際數(shù)據(jù)的傳輸。
對(duì) 于IRP_MJ_READ讀請(qǐng)求,I/O管理器還把IRP的UserBuffer域設(shè)置 成調(diào)用者緩沖區(qū)的用戶空間地址。當(dāng)請(qǐng)求完成時(shí),I/O管理器利用 這個(gè)地址將數(shù)據(jù)從驅(qū)動(dòng)程序的系統(tǒng)空間拷貝回調(diào)用者的緩沖區(qū)。對(duì) 于IRP_MJ_WRITE寫(xiě)請(qǐng)求,UserBuffer被設(shè)置為NULL,并把用戶緩沖 區(qū)的數(shù)據(jù)拷貝到系統(tǒng)緩沖區(qū)中。
2、 直接I/O(DO_DIRECT_IO)
I/O 管理器首先檢查用戶緩沖區(qū)的可訪問(wèn)性,并在物理內(nèi)存中鎖定它。然后它為該緩沖區(qū)創(chuàng)建一個(gè)內(nèi)存描述表(MDL),并把MDL的地址 存放在IRP的MdlAddress域中。AssociatedIrp.SystemBuffer和 UserBuffer都被設(shè)置為NULL。驅(qū)動(dòng)程序可以調(diào)用函數(shù) MmGetSystemAddressForMdl得到用戶緩沖區(qū)的系統(tǒng)空間地址,從而 進(jìn)行數(shù)據(jù)操作。這個(gè)函數(shù)將調(diào)用者的緩沖區(qū)映射到非份頁(yè)的地址空 間。驅(qū)動(dòng)程序完成I/O請(qǐng)求后,系統(tǒng)自動(dòng)從系統(tǒng)空間解除緩沖區(qū)的映射。
3、 這兩種方法都不是
這種情況比較少用,因?yàn)檫@需要驅(qū)動(dòng)程序自己來(lái)處理緩沖問(wèn)題。 I/O管理器僅把調(diào)用者緩沖區(qū)的用戶空間地址放到IRP的UserBuffer 域中。我們并不推薦這種方式。

IOCTL緩沖區(qū)的緩沖策略
IOCTL請(qǐng)求涉及來(lái)自調(diào)用者的輸入緩沖區(qū)和返回到調(diào)用者的輸出 緩沖區(qū)。為了理解IOCTL請(qǐng)求,我們先來(lái)看看WIN32 API DeviceIoControl函數(shù)的原型。
BOOL DeviceIoControl (
HANDLE hDevice, // 設(shè)備句柄
DWORD dwIoControlCode, // IOCTL請(qǐng)求操作代碼
LPVOID lpInBuffer, // 輸入緩沖區(qū)地址
DWORD nInBufferSize, // 輸入緩沖區(qū)大小
LPVOID lpOutBuffer, // 輸出緩沖區(qū)地址
DWORD nOutBufferSize, // 輸出緩沖區(qū)大小
LPDWORD lpBytesReturned, // 存放返回字節(jié)數(shù)的指針
LPOVERLAPPED lpOverlapped // 用于同步操作的Overlapped結(jié)構(gòu)體指針
);
IOCTL請(qǐng)求有四種緩沖策略,下面一一介紹。
1、 輸入輸出緩沖I/O(METHOD_BUFFERED)
I/O 管理器首先分配一個(gè)非分頁(yè)池,它足夠大地存放調(diào)用者的輸入或輸出緩沖區(qū)(不管哪個(gè)更大)。非分頁(yè)緩沖區(qū)的地址放在IRP的 AssociatedIrp.SystemBuffer域中,然后把IOCTL的輸入數(shù)據(jù)拷貝 到這個(gè)非份頁(yè)緩沖區(qū)中,并把IRP的UserBuffer域設(shè)置成調(diào)用者輸出緩沖區(qū)的用戶空間地址。當(dāng)驅(qū)動(dòng)程序完成IOCTL請(qǐng)求時(shí),I/O管理器將這個(gè) 非份頁(yè)緩沖區(qū)中的數(shù)據(jù)拷貝到調(diào)用者的輸出緩沖區(qū)。注意這里同一個(gè)非份頁(yè)池同時(shí)用于輸入和輸出緩沖區(qū),因此驅(qū)動(dòng)程序在向緩沖區(qū)寫(xiě)東西之前應(yīng)該把輸入的所有數(shù) 據(jù)讀出來(lái)。
2、 直接輸入緩沖輸出I/O(METHOD_IN_DIRECT)
I/O管理器首先檢查調(diào)用者輸入緩沖區(qū)的可訪問(wèn)性, 并在物理內(nèi)存中將其鎖定。然后為該輸入緩沖區(qū)創(chuàng)建一個(gè)MDL,并把指定該MDL的指針存放到IRP的MdlAddress域中。同時(shí),I/O管理器還在非 份頁(yè)池中分配一輸出緩沖區(qū),并把這個(gè)緩沖區(qū)的地址存放在IRP的AssociatedIrp.SystemBuffer域中,并把IRP的 UserBuffer域設(shè)置成調(diào)用者輸出緩沖區(qū)的用戶空間地址。當(dāng)驅(qū)動(dòng)程序完成IOCTL請(qǐng)求時(shí),I/O管理器將非份頁(yè)緩沖區(qū)中的數(shù)據(jù)拷貝到調(diào)用者的輸出 緩沖區(qū)。
3、 緩沖輸入直接輸出I/O(METHOD_OUT_DIRECT)
I/O管理器首先檢查調(diào)用者輸出緩沖區(qū)的可訪問(wèn)性,并在 物理內(nèi)存中將其鎖定。然后為該輸出緩沖區(qū)創(chuàng)建一個(gè)MDL,并把指定該MDL的指針存放到IRP的MdlAddress域中。同時(shí),I/O管理器還在非份頁(yè) 池中分配一輸入緩沖區(qū),并把這個(gè)緩沖區(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域中。這兩個(gè)地址都是用戶空間地 址。
從 上面的說(shuō)明可以看出,在執(zhí)行緩沖I/O時(shí),I/O管理器將在非份頁(yè)池 中分配內(nèi)存,如果調(diào)用者的緩沖區(qū)比較大時(shí),分配的非份頁(yè)池也將 比較大。非份頁(yè)池是系統(tǒng)比較寶貴的資源,因此,如果調(diào)用者的緩 沖區(qū)比較大時(shí),我們一般采用直接I/O的方式(例如磁盤(pán)讀寫(xiě)請(qǐng)求等), 這樣不僅節(jié)省系統(tǒng)資源,另一方面由于省去了I/O管理器在系統(tǒng)緩沖 區(qū)和調(diào)用者緩沖區(qū)之間的數(shù)據(jù)拷貝,也提高了效率,這對(duì)存在大量 數(shù)據(jù)傳送的驅(qū)動(dòng)程序尤其明顯。
可以注意到DDK中的Samples下,幾乎所有的例程的讀寫(xiě)請(qǐng)求都是直 接I/O的,而對(duì)于IOCTL請(qǐng)求則是緩沖區(qū)I/O的居多。

開(kāi)始驅(qū)動(dòng)程序設(shè)計(jì)
下面的文字是從Microsoft的DDK幫助中節(jié)選出來(lái)的,它讓我們明 白在開(kāi)始設(shè)計(jì)驅(qū)動(dòng)程序應(yīng)該注意些什么問(wèn)題,這些都是具有普遍 意義的開(kāi)發(fā)準(zhǔn)則。應(yīng)該支持哪些I/O請(qǐng)求在開(kāi)始寫(xiě)任何代碼之前, 應(yīng)該首先確定我們的驅(qū)動(dòng)程序應(yīng)該處理哪些IRP例程。
如果你在設(shè)計(jì)一個(gè)設(shè)備驅(qū)動(dòng)程序,你應(yīng)該支持和其他相同類型 設(shè)備的NT驅(qū)動(dòng)程序相同的IRP_MJ_XXX和IOCTL請(qǐng)求代碼。
如 果你是在設(shè)計(jì)一個(gè)中間層NT驅(qū)動(dòng)程序,應(yīng)該首先確認(rèn)你下層 驅(qū)動(dòng)程序所管理的設(shè)備,因?yàn)橐粋€(gè)高層的驅(qū)動(dòng)程序必須具有低層 驅(qū)動(dòng)程序絕大多數(shù)IRP_MJ_XXX例程入口。高層驅(qū)動(dòng)程序在接到I/O 請(qǐng)求時(shí),在確定自身IRP當(dāng)前堆棧單元參數(shù)有效的前提下 ,設(shè)置好IRP中下一個(gè)低層驅(qū)動(dòng)程序的堆棧單元,然后再調(diào)用IoCallDriver 將請(qǐng)求傳遞給下層驅(qū)動(dòng)程序處理。
一旦決定好了你的驅(qū)動(dòng) 程序應(yīng)該處理哪些IRP_MJ_XXX,就可以開(kāi)始 確定驅(qū)動(dòng)程序應(yīng)該有多少個(gè)Dispatch例程。當(dāng)然也可以考慮把某些 RP_MJ_XXX處理的例程合并為同一例程處理。例如在ChangerDisk和 VDisk里,對(duì)IRP_MJ_CREATE和IRP_MJ_CLOSE處理的例程就是同一函數(shù)。 對(duì)IRP_MJ_READ和IRP_MJ_WRITE處理的例程也是同一個(gè)函數(shù)。
應(yīng)該有多少個(gè)Device對(duì)象?
一個(gè)驅(qū)動(dòng)程序必須 為它所管理的每個(gè)可能成為I/O請(qǐng)求的目標(biāo)的物理和邏輯設(shè)備創(chuàng)建一個(gè)命名Device對(duì)象。一些低層的驅(qū)動(dòng)程序還可能要?jiǎng)?chuàng)建一些不確定數(shù)目的Device 對(duì)象。例如一個(gè)硬盤(pán)驅(qū)動(dòng)程序必須為每一個(gè)物理硬盤(pán)創(chuàng)建一個(gè)Device對(duì)象,同時(shí)還必須為每個(gè)物理磁盤(pán)上的每個(gè)邏輯分區(qū)創(chuàng)建一個(gè)Device對(duì)象。
一 個(gè)高層驅(qū)動(dòng)驅(qū)動(dòng)程序必須為它所代表的虛擬設(shè)備創(chuàng)建一個(gè)Device 對(duì)象,這樣更高層的驅(qū)動(dòng)程序才能連接它們的Device對(duì)象到這個(gè)驅(qū)動(dòng)程序的Device對(duì)象。另外,一個(gè)高層驅(qū)動(dòng)程序通常為它低層驅(qū)動(dòng) 程序所創(chuàng)建的Device對(duì)象創(chuàng)建一系列的虛擬或邏輯Device對(duì)象。
盡管你可以分階段來(lái)設(shè)計(jì)你的驅(qū)動(dòng)程序,因此一個(gè)處在開(kāi)發(fā)階段的 驅(qū)動(dòng)程序不必一開(kāi)始就創(chuàng)建出所有它將要處理的所有Device對(duì)象。 但從一開(kāi)始就確定好你最終要?jiǎng)?chuàng)建的所有Device對(duì)象將有助于設(shè)計(jì)者所要解決的任何同步問(wèn)題。另外,確定所要?jiǎng)?chuàng)建的Device對(duì)象還有助于你定義 Device對(duì)象的Device Extension的內(nèi)容和數(shù)據(jù)結(jié)構(gòu)。
開(kāi)始驅(qū)動(dòng)程序開(kāi)發(fā)
驅(qū)動(dòng)程序的開(kāi)發(fā)是一個(gè)從粗到細(xì)逐步求精的 過(guò)程。NT DDK的src 目錄下有一個(gè)龐大的樣板代碼,幾乎覆蓋了所有類型的設(shè)備驅(qū)動(dòng)程序、高層驅(qū)動(dòng)程序和過(guò)濾器驅(qū)動(dòng)程序。在開(kāi)始開(kāi)發(fā)你的驅(qū)動(dòng)程序之前,你應(yīng)該在這個(gè)樣板庫(kù)下面尋 找是否有和你所要開(kāi)發(fā)的類似類型的例程。例如我們所開(kāi)發(fā)的驅(qū)動(dòng)程序,雖然DDK對(duì)USB描述得不是很詳細(xì),我們還是可以在src\storage class目錄發(fā)現(xiàn)很多和USB設(shè)備有關(guān)的驅(qū)動(dòng)程序。下面我們來(lái)看開(kāi)發(fā)驅(qū)動(dòng)程序的基本步驟。
最簡(jiǎn)的驅(qū)動(dòng)程序框架
1、 寫(xiě)一個(gè)DriverEntry例程,在里面調(diào)用IoCreateDevice創(chuàng)建 一個(gè)Device對(duì)象。
2、 寫(xiě)一個(gè)處理IRP_MJ_CREATE請(qǐng)求的Dispatch例程的基本框架 (參見(jiàn)DDK Kernel-Mode Drivers 4.4.3描述的一個(gè)DispatchCreate 例程所要完成的最基本工作。當(dāng)然寫(xiě)了DispatchCreate例程后, 要在DriverEntry例程為IRP_MJ_CREATE初始化例程入口)。如果驅(qū)動(dòng)程序創(chuàng)建了多于一個(gè)Device對(duì)象,則必須為 IRP_MJ_CLOSE 請(qǐng)求寫(xiě)一個(gè)例程,該例程通常情況下可以和DispatchCreate共用一個(gè)例程,參見(jiàn)參見(jiàn)DDK Kernel-Mode Drivers 4.4.3。
3、 編譯連接你的驅(qū)動(dòng)程序。
用下面的方法來(lái)測(cè)試你的驅(qū)動(dòng)程序。
首先按上面介紹的方法安裝好驅(qū)動(dòng)程序。
其 次我們還得為NT邏輯設(shè)備名稱和目標(biāo)Device對(duì)象名稱之間建立 起符號(hào)連接,我們?cè)谇懊嬉呀?jīng)知道Device對(duì)象名稱對(duì)WIN32用戶模式 是不可見(jiàn)的,是不能直接通過(guò)API來(lái)訪問(wèn)的,WIN 32 API只能訪問(wèn)NT 邏輯設(shè)備名稱。我們可以通過(guò)修改注冊(cè)表來(lái)建立這兩種名稱之間的符 號(hào)連接。運(yùn)行REGEDT32.EXE在\HKEY_LOCAL_MACHINE\ System CurrentControlSet\Control\ Session Manager\ DOS Devices下建立起符號(hào)連接(這種符號(hào)連接也可以在驅(qū)動(dòng)程序里調(diào)用函數(shù) IoCreateSymbolicLink來(lái)創(chuàng)建)。
重新啟動(dòng)系統(tǒng)。
編寫(xiě)一個(gè)簡(jiǎn)單的測(cè)試程序調(diào)用WIN32API CreateFile函數(shù)以剛才你命名的NT邏輯設(shè)備名打開(kāi)這個(gè)設(shè)備。如果打開(kāi)成功,那么你也就成功地寫(xiě)出了一個(gè)最簡(jiǎn)單的驅(qū)動(dòng)程序了。
支持更多的設(shè)備I/O請(qǐng)求
例 如你的驅(qū)動(dòng)程序可能需要對(duì)IRP_MJ_READ請(qǐng)求做出響應(yīng)(完成后可用WIN32 API ReadFile函數(shù)進(jìn)行測(cè)試)。如果你的驅(qū)動(dòng)程序需要能夠手工卸載,那么還必須對(duì)IRP_MJ_CLOSE做出響應(yīng)。為你所需要處理 IRP_MJ_XXX寫(xiě)好處理例程,并在DriverEntry里面初始化好這些例 程入口。
一個(gè)低層的驅(qū)動(dòng)程序可能需要最起碼一個(gè)StartIo,ISR和DpcForIsr 例程,可能需要一個(gè)SynchCritSection例程,如果設(shè)備使用了DMA, 那么可能還需要一個(gè)AdapterControl例程。關(guān)于這些例程,請(qǐng)參考 DDK相應(yīng)文檔。
對(duì)于高層驅(qū)動(dòng)程序可能需要一個(gè)或多個(gè)IoCompletion例程,最起碼 完成檢查I/O狀態(tài)塊然后調(diào)用IoCompleteRequest的工作。 如果需要,還要對(duì)Device Extension數(shù)據(jù)結(jié)構(gòu)和內(nèi)容做些修改

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買(mǎi)等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多