jackyhwei 發(fā)布于 2011-01-28 09:59 點(diǎn)擊:
本篇我們介紹開發(fā)之前的準(zhǔn)備工作,包括開發(fā)環(huán)境準(zhǔn)備、預(yù)備知識(shí)。 開發(fā)環(huán)境準(zhǔn)備 對(duì)于開發(fā)WDM驅(qū)動(dòng)程序來(lái)說(shuō),我們有以下三個(gè)常用組合: 1.直接使用Windows DDK 2.使用DriverStudio 3.使用Windriver 下面我們分別比較三種方式的優(yōu)缺點(diǎn)。 第一種:開發(fā)難度大一些,而且有很多煩瑣的工作要作,大部分都是通用的基礎(chǔ)性的工作。但如果選用這種方式的話你將對(duì)整個(gè)體系結(jié)構(gòu)會(huì)有很好的理解和把握。 第二種:難度低一些,工具軟件已經(jīng)幫你作了很多基礎(chǔ)性的工作。也封裝了一些細(xì)節(jié),你只要專心去作你需要的操作,但由于封裝的問題,可能會(huì)帶來(lái)一些bug。有可能導(dǎo)致項(xiàng)目的失敗。 第三種:幾乎沒有難度(從開發(fā)驅(qū)動(dòng)的角度)。很容易,但只能開發(fā)硬件相關(guān)的驅(qū)動(dòng),事實(shí)上你寫的只是定制和調(diào)用它提供的通用驅(qū)動(dòng)而已。效率上有問題。工作頻率不是很高。但開發(fā)花費(fèi)的時(shí)間很少。是上面的幾 乃至幾十分之一。 建議: 用windriver作驅(qū)動(dòng)程序的原型,用driverstudio作最終發(fā)行的驅(qū)動(dòng)程序,如果驅(qū)動(dòng)程序很復(fù)雜的話,建議直接使用ddk開發(fā)。 上面的幾種情況都需要vc++作為輔助開發(fā)環(huán)境。(ddk也可以直接用命令行工具,但比較煩),前兩種情況都需要ddk。開發(fā)時(shí)間上,第一種最長(zhǎng),第三種最短,第二種可以認(rèn)為是前面兩種方案的折衷。 如果更具體一點(diǎn)的話,我們可以把以上三種形式比作三種開發(fā)工具,那就是 ms c,vc++,Vb。 如果SDK沒bug的話,用ms c開發(fā)的純sdk程序的bug是最少的。Vc++由于對(duì)sdk進(jìn)行了封裝,必然會(huì)引出一些新的bug。Vb開發(fā)程序雖然快了一些,但運(yùn)行效率比前兩種方式差了很多。 這樣說(shuō)明這三種方式的話,大家一定會(huì)明白了。 我們?yōu)榱撕?jiǎn)便起見,使用ddk+VC的方式。 首 先,我們按正常方式安裝好vc++ 6.0。不過據(jù)微軟文檔說(shuō)ddk98只支持vc++ 5.0。我手里沒有vc++5.0,在vc++6.0下試了一 下,證明可以使用,不過設(shè)置很困難的。當(dāng)然,如果你不覺得煩的話,也可以直接用build工具即可。在安裝好vc++后再安裝ddk開發(fā)包,這樣不容易出 錯(cuò)。 如果你使用DriverStudio開發(fā)包,請(qǐng)先安裝好vc++6.0,然后再安裝它,在安裝softice時(shí)注意選擇通用顯卡驅(qū)動(dòng),這樣一般情況下都能正常使用。
預(yù)備知識(shí) 在開發(fā)環(huán)境安裝完成后,我們將要步入開發(fā)過程。在實(shí)際動(dòng)手之前,我們先要學(xué)習(xí)一些預(yù)備知識(shí)。 在設(shè)備驅(qū)動(dòng)程序中,要作很多工作,包括初始化,設(shè)備對(duì)象創(chuàng)建等等工作,其中一些是很重要的,必須實(shí)現(xiàn),一些是可選的,如果你的驅(qū)動(dòng)對(duì)這些功能的要求不是很高的話,可以不實(shí)現(xiàn)。 要實(shí)現(xiàn)的功能主要有以下幾個(gè): 初始化 創(chuàng)建和刪除設(shè)備 I/O請(qǐng)求的超時(shí)處理 I/O請(qǐng)求的撤消 訪問硬件資源 處理Windows的輸入/輸出請(qǐng)求 串行化對(duì)設(shè)備的訪問 調(diào)用其它驅(qū)動(dòng)程序 處理一個(gè)可熱拔插的設(shè)備被加入或刪除的情況 處理電源管理請(qǐng)求 使用Windows管理診斷功能 處理Windows的打開和關(guān)閉文件句柄的請(qǐng)求 從 實(shí)際工作情況來(lái)看,只有初始化模塊是必不可少的。但是只有初始化模塊的驅(qū)動(dòng)程序什么工作也干不了,只能說(shuō)它僅僅是一個(gè)概念意義上的驅(qū)動(dòng)程序而已,好比失去 感覺的植物人(軀體存在,但已經(jīng)沒有了意志)。通常情況下,一個(gè)完整的驅(qū)動(dòng)程序至少要能響應(yīng)用戶態(tài)程序發(fā)出的I/O訪問請(qǐng)求。大多數(shù)情況下驅(qū)動(dòng)程序要訪問 它們所支持的硬件資源,并且要支持簡(jiǎn)單的電源管理功能和Windows管理診斷功能或能向系統(tǒng)日志寫入信息。 WDM驅(qū)動(dòng)程序通常由PnP管理器載入內(nèi)存,然后調(diào)用它之中的AddDevice例程來(lái)創(chuàng)建設(shè)備。當(dāng)然,在此時(shí)還要需要一個(gè)inf安裝文件而來(lái)指明該驅(qū)動(dòng)程序需要的一些參數(shù)。 系 統(tǒng)內(nèi)核通常通過向驅(qū)動(dòng)程序發(fā)送IRP包來(lái)運(yùn)行驅(qū)動(dòng)程序中的實(shí)現(xiàn)代碼。我們以Windows向設(shè)備發(fā)出的ReadFile調(diào)用為例:此時(shí)Windows向驅(qū) 動(dòng)程序發(fā)出一個(gè)“讀”請(qǐng)求的IRP包,讀取緩沖區(qū)的大小和位置作為IRP包中的參數(shù)指定(IRP實(shí)際上是一個(gè)數(shù)據(jù)結(jié)構(gòu),包含幾個(gè)域)。如果你作過 Windows的程序,特別是用 VC作過開發(fā)的話,你應(yīng)該知道,windows用戶態(tài)應(yīng)用程序是消息驅(qū)動(dòng)的,應(yīng)用程序中的代碼是通過消息機(jī)制的觸發(fā)而獲 得運(yùn)行的機(jī)會(huì)的,需要的參數(shù)是通過消息的域(wParam、lParam)傳給應(yīng)用程序。實(shí)際上驅(qū)動(dòng)程序的動(dòng)作還是可以看作是一種消息驅(qū)動(dòng)方式,只不過內(nèi) 核態(tài)的“消息”已經(jīng)不再稱作消息,而是被稱作I/O請(qǐng)求包(IRP)。 驅(qū)動(dòng)程序通常使用DriverEntry作為入口點(diǎn),與我們?cè)赪indows應(yīng)用程序中定義的WinMain相似。通常情況下,它是驅(qū)動(dòng)程序的默認(rèn)入口點(diǎn)。 注: 標(biāo)準(zhǔn)Build腳本將驅(qū)動(dòng)程序入口點(diǎn)定為DriverEntry,你最好遵守這個(gè)假設(shè),否則必須修改Build腳本。 在 這個(gè)入口函數(shù)中,我們必須作必要的初始化設(shè)置,并設(shè)置必要的回調(diào)函數(shù)。我們可以這樣理解:我們用c++(特別是用VC++)時(shí),我們?cè)陬惖臉?gòu)造函數(shù)中要作 必要的初始化操作,并要在類中作消息處理方法的映射,這樣才能讓需要的消息得到適當(dāng)?shù)奶幚?。我們也要在DriverEntry例程中設(shè)置必要的IRP處理 函數(shù)。 一般情況下,DriverEntry例程要設(shè)置以下幾個(gè)IRP處理函數(shù): DriverUnload 指向驅(qū)動(dòng)程序的清除例程。I/O管理器會(huì)在卸載驅(qū)動(dòng)程序前調(diào)用該例程。通常,WDM驅(qū)動(dòng)程序的DriverEntry例程一般不分配任何資源,所以DriverUnload例程也沒有什么清除工作要做。 DriverExtension->AddDevice 指向驅(qū)動(dòng)程序的AddDevice函數(shù)。PnP管理器將為每個(gè)硬件實(shí)例調(diào)用一次AddDevice例程。這樣將創(chuàng)建一個(gè)該設(shè)備對(duì)象。 DriverStartIo 如果驅(qū)動(dòng)程序使用標(biāo)準(zhǔn)的IRP排隊(duì)方式,應(yīng)該設(shè)置該成員,使其指向驅(qū)動(dòng)程序的StartIo例程。如果你不理解什么是“標(biāo)準(zhǔn)”排隊(duì)方式,不要著急,能后的教程中你就會(huì)完全明白,許多驅(qū)動(dòng)程序都使用這種方法。 MajorFunction 是一個(gè)指針數(shù)組,I/O管理器把每個(gè)數(shù)組元素都初始化成指向一個(gè)空函數(shù),這個(gè)空函數(shù)僅返回失敗。驅(qū)動(dòng)程序可能僅需要處理幾種類型的IRP,所以至少應(yīng)該設(shè)置與那幾種IRP類型相對(duì)應(yīng)的指針元素,使它們指向相應(yīng)的派遣函數(shù)。 下面是一段DriverEntry例程的示例: - extern "C" NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject
- , IN PUNICODE_STRING RegistryPath)
- {
- DriverObject->DriverUnload = DriverUnload; <--1
- DriverObject->DriverExtension->AddDevice = AddDevice;
- DriverObject->DriverStartIo = StartIo;
- DriverObject->MajorFunction[IRP_MJ_PNP] = DispatchPnp;
-
- DriverObject->MajorFunction[IRP_MJ_POWER] = DispatchPower;
- DriverObject->MajorFunction[IRP_MJ_SYSTEM_CONTROL] = DispatchWmi;
- ... <--3
- servkey.Buffer = (PWSTR) ExAllocatePool(PagedPool
- , RegistryPath->Length + sizeof(WCHAR)); <--4
- if (!servkey.Buffer)
- return STATUS_INSUFFICIENT_RESOURCES;
- servkey.MaximumLength = RegistryPath->Length + sizeof(WCHAR);
- RtlCopyUnicodeString(&servkey, RegistryPath);
- return STATUS_SUCCESS; <--5
- }
1. 前三條語(yǔ)句為驅(qū)動(dòng)程序的其它入口點(diǎn)設(shè)置了函數(shù)指針。在這里,我們用了能表達(dá)其功能的名字命名了這些函數(shù):DriverUnload、AddDevice、StartIo。 2. 每 個(gè)WDM驅(qū)動(dòng)程序必須能處理PNP、POWER、SYSTEM_CONTROL這三種請(qǐng)求;應(yīng)該在這里為這些請(qǐng)求指定派遣函數(shù)。在早期的 Windows 2000 DDK中,IRP_MJ_SYSTEM_CONTROL曾被稱作IRP_MJ_WMI,所以我把系統(tǒng)控制派遣函數(shù)命名為 DispatchWmi。 3. 在省略號(hào)處,你可以插入設(shè)置其它MajorFunction指針的代碼。 4. 如果驅(qū)動(dòng)程序需要訪問設(shè)備的服務(wù)鍵,可以在這里備份RegistryPath串。例如,如果驅(qū)動(dòng)程序要作為WMI生產(chǎn)者,則需要備份這個(gè)串。這里我假設(shè)已經(jīng)在某處聲明了一個(gè)類型為UNICODE_STRING的全局變量servkey。 5. 返回STATUS_SUCCESS指出函數(shù)成功。如果函數(shù)失敗,應(yīng)該返回NTSTATUS.H中的一個(gè)錯(cuò)誤代碼,或者返回用戶定義的錯(cuò)誤代碼。STATUS_SUCCESS的值為0。 關(guān)于DriverUnload例程的補(bǔ)充說(shuō)明: 在WDM驅(qū)動(dòng)程序中,DriverUnload例程的作用就是釋放DriverEntry例程在全局初始化過程中申請(qǐng)的任何資源,但它幾乎沒什么可做。如果你在DriverEntry中備份了RegistryPath串,應(yīng)該在這里釋放備份所占用的內(nèi)存: VOID DriverUnload(PDRIVER_OBJECT DriverObject) { RtlFreeUnicodeString(&servkey);//釋放先前申請(qǐng)的資源 } 如果DriverEntry例程返回一個(gè)失敗狀態(tài)代碼,系統(tǒng)將不再調(diào)用DriverUnload例程。所以,不能讓DriverEntry例程出錯(cuò)后產(chǎn)生任何副作用,必須在它返回錯(cuò)誤代碼前消除副作用(釋放掉申請(qǐng)的系統(tǒng)資源)。 一般情況下,一個(gè)驅(qū)動(dòng)程序可以被多個(gè)設(shè)備利用。WDM驅(qū)動(dòng)程序有一個(gè)特殊的AddDevice函數(shù),PnP管理器為每個(gè)設(shè)備實(shí)例調(diào)用該函數(shù)。以下為該函數(shù)的原型定義: NTSTATUS AddDevice(PDRIVER_OBJECT DriverObject, PDEVICE_OBJECT pdo) { } DriverObject參數(shù)指向一個(gè)驅(qū)動(dòng)程序?qū)ο螅褪悄阍贒riverEntry入口例程中初始化的那個(gè)驅(qū)動(dòng)程序?qū)ο?。pdo參數(shù)指向設(shè)備堆棧底部的物理設(shè)備對(duì)象。 對(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)用程序能夠發(fā)現(xiàn)設(shè)備的存在。另外,還可以給出設(shè)備名并創(chuàng)建符號(hào)連接。 3. 初始化設(shè)備擴(kuò)展和設(shè)備對(duì)象的Flag成員。 4. 調(diào)用IoAttachDeviceToDeviceStack函數(shù)把新設(shè)備對(duì)象放到堆棧上。 下面我將詳細(xì)解釋這些步驟。 創(chuàng)建設(shè)備對(duì)象 調(diào)用IoCreateDevice函數(shù)創(chuàng)建設(shè)備對(duì)象,例如: - PDEVICE_OBJECT fdo;
- NTSTATUS status = IoCreateDevice(DriverObject,
- sizeof(DEVICE_EXTENSION),
- NULL,
- FILE_DEVICE_UNKNOWN,
- FILE_DEVICE_SECURE_OPEN,
- FALSE,
- &fdo);
第一個(gè)參數(shù)(DriverObject) 就是AddDevice的第一個(gè)參數(shù)。該參數(shù)用于在驅(qū)動(dòng)程序和新設(shè)備對(duì)象之間建立連接,這樣I/O管理器就可以向設(shè)備發(fā)送指定的IRP。 第二個(gè)參數(shù)是設(shè)備擴(kuò)展結(jié)構(gòu)的大小。I/O管理器自動(dòng)分配這個(gè)內(nèi)存,并把設(shè)備對(duì)象中的DeviceExtension指針指向這塊內(nèi)存。 第三個(gè)參數(shù)在本例中為NULL。它可以是命名該設(shè)備對(duì)象的UNICODE_STRING串的地址。決定是否命名設(shè)備對(duì)象以及以什么名字命名還需要仔細(xì)考慮,我將在后面深入認(rèn)真地討論這個(gè)問題。 第 四個(gè)參數(shù)(FILE_DEVICE_UNKNOWN) 是設(shè)備類型。這個(gè)值可以被設(shè)備硬件鍵(注冊(cè)表中包含該硬件信息的鍵值)或類鍵(注冊(cè)表中包含該類驅(qū) 動(dòng)信息的鍵值)中的可替換值(overriding values)所替代,如果這兩個(gè)鍵都含有該參數(shù)的替換值,那么硬件鍵中的可替換值具有更高的優(yōu)先 權(quán)。對(duì)于屬于某個(gè)已存在類的設(shè)備,必須在這些地方指定正確的值,因?yàn)轵?qū)動(dòng)程序與外圍系統(tǒng)的交互需要依靠這個(gè)值。另外,設(shè)備對(duì)象的默認(rèn)安全設(shè)置也依靠這個(gè)設(shè) 備類型值。 第五個(gè)參數(shù)(FILE_DEVICE_SECURE_OPEN) 為設(shè)備對(duì)象提供Characteristics標(biāo)志。這些標(biāo)志主要關(guān) 系到塊存儲(chǔ)設(shè)備(如軟盤、CDROM、Jaz等等)。未公開標(biāo)志位FILE_AUTOGENERATED_DEVICE_NAME僅用于內(nèi)部使用,并不是 DDK文檔忘記提到該標(biāo)志。這個(gè)參數(shù)同樣也能被硬件鍵或類鍵中的對(duì)應(yīng)值替換,如果兩個(gè)值都存在,那么硬件鍵中的可替換值具有更高的優(yōu)先權(quán)。 第六個(gè)參數(shù)(FALSE) 指出設(shè)備是否是排斥的。通常,對(duì)于排斥設(shè)備,I/O管理器僅允許打開該設(shè)備的一個(gè)句柄。這個(gè)值同樣也能被注冊(cè)表中硬件鍵和類鍵中的值替換,如果兩個(gè)可替換值都存在,硬件鍵中的可替換值具有更高的優(yōu)先權(quán)。 注意 排斥屬性僅關(guān)系到打開請(qǐng)求的目標(biāo)是命名設(shè)備對(duì)象。如果你遵守Microsoft推薦的WDM驅(qū)動(dòng)程序設(shè)計(jì)方針,沒有為設(shè)備對(duì)象命名,那么打開請(qǐng)求將直接指向 PDO(物理設(shè)備對(duì)象)。PDO通常不能被標(biāo)記為排斥,因?yàn)榭偩€驅(qū)動(dòng)程序沒有辦法知道設(shè)備是否需要排斥特征。把PDO標(biāo)為排斥的唯一的機(jī)會(huì)在注冊(cè)表中,即 設(shè)備硬件鍵或類鍵的Properties子鍵含有Exclusive可替換值。為了完全避免依賴排斥屬性,你應(yīng)該利用IRP_MJ_CREAT例程彈出任 何有違規(guī)行為的打開請(qǐng)求。 第七個(gè)參數(shù)(&fdo) 是存放設(shè)備對(duì)象指針的地址,IoCreateDevice函數(shù)使用該變量保存剛創(chuàng)建的設(shè)備對(duì)象的地址。 如 果IoCreateDevice由于某種原因失敗,則它返回一個(gè)錯(cuò)誤代碼,不改變fdo中的值。如果IoCreateDevice函數(shù)返回成功代碼,那么 它同時(shí)也設(shè)置了fdo指針。然后我們進(jìn)行到下一步,初始化設(shè)備擴(kuò)展,做與創(chuàng)建新設(shè)備對(duì)象相關(guān)的其它工作,如果在這之后又發(fā)現(xiàn)了錯(cuò)誤,那么在返回前應(yīng)先釋放 剛創(chuàng)建的設(shè)備對(duì)象并返回狀態(tài)碼。見下面例子代碼: - NTSTATUS status = IoCreateDevice(...);
- if (!NT_SUCCESS(status))
- return status;
- ...
- if (<some other error discovered>)
- {
- IoDeleteDevice(fdo);
- return status;
- }
為設(shè)備命名 Windows 使 用對(duì)象管理器集中管理系統(tǒng)中的大量的內(nèi)部數(shù)據(jù)結(jié)構(gòu)(每個(gè)對(duì)象在系統(tǒng)中都表現(xiàn)為一個(gè)數(shù)據(jù)結(jié)構(gòu)),包括驅(qū)動(dòng)程序?qū)ο蠛驮O(shè)備對(duì)象。為了便于區(qū)別,每個(gè)對(duì)象都有名 稱,對(duì)象管理器用一個(gè)層次化的命名空間來(lái)管理這些名稱。圖中是DevView(一個(gè)設(shè)備觀察工具,在驅(qū)動(dòng)開發(fā)網(wǎng) 站<http://www.有下載>)顯示的頂層對(duì)象名。此工具以文件夾形式顯示的對(duì)象是目錄對(duì)象,它可 以包含子目錄或常規(guī)對(duì)象,其它圖標(biāo)則代表正常對(duì)象。 通常設(shè)備對(duì)象都把自己的名字放到\Device目錄中。在Windows 2000中,設(shè)備的 名稱有兩個(gè)用途。第一個(gè)用途,通過命名后,其它內(nèi)核模式部件可以通過調(diào)用IoGetDeviceObjectPointer函數(shù)找到該設(shè)備,找到設(shè)備對(duì)象 后,就可以向該設(shè)備的驅(qū)動(dòng)程序發(fā)送IRP(I/O請(qǐng)求包)。 另一個(gè)用途,允許用戶態(tài)的應(yīng)用程序打開命名設(shè)備的句柄,這樣它們就可以向驅(qū)動(dòng)程序發(fā)送 IRP。應(yīng)用程序可以使用標(biāo)準(zhǔn)的CreateFile API打開命名設(shè)備句柄,然后用ReadFile、WriteFile,和 DeviceIoControl向驅(qū)動(dòng)程序發(fā)出請(qǐng)求(關(guān)于這些API函數(shù)的詳細(xì)說(shuō)明和使用,我們將在后面的文章中詳述)。應(yīng)用程序打開設(shè)備句柄時(shí)使 用\\.\路徑前綴。在C/C++語(yǔ)言程序中使用時(shí),需要轉(zhuǎn)化為’\\\\.\\’,這是由語(yǔ)法規(guī)定的。在內(nèi)部,I/O管理器在執(zhí)行名稱搜索前自動(dòng)把 \\.\轉(zhuǎn)換成\??\。為了把\??目錄中的名字與名字在其它目錄(例如,在\Device目錄)中的對(duì)象相連接,對(duì)象管理器實(shí)現(xiàn)了一種稱為符號(hào)連接 (symbolic link)的對(duì)象。 符號(hào)連接 符號(hào)連接有點(diǎn)象WIDOWS桌面上的快捷方式,符號(hào)連接在Windows NT/2K中 的主要用途是把處于列表前面的DOS形式的名稱連接到設(shè)備上。符號(hào)連接可以使對(duì)象管理器在分析一個(gè)名稱時(shí)能跳到命名空間的某個(gè)地方。例如我們通常見到的C 盤,其實(shí)它是就是一個(gè)設(shè)備(磁盤)的符號(hào)鏈接。如果你用過unix/linux操作系統(tǒng)的話,你會(huì)對(duì)符號(hào)鏈接有所理解,這里的符號(hào)鏈接相當(dāng)于unix /linux系統(tǒng)中的軟鏈接。 術(shù)語(yǔ) 類鍵 所有設(shè)備類的類鍵都出現(xiàn)在HKLM\System\CurrentControlSet\Control\Class鍵中。它們的鍵名是由Microsoft賦予的GUID值。 硬件鍵 硬件鍵包含單個(gè)設(shè)備的信息。
在上期中我們講了符號(hào)連接,在應(yīng)用層開發(fā)中我們可以調(diào)用以下函數(shù)來(lái)創(chuàng)建一個(gè)\??目錄下的符號(hào)鏈接: BOOL okay = DefineDosDevice(DDD_RAW_TARGET_PATH, "barf", "\\Device\\SECTEST_0"); 調(diào)用成功后,將會(huì)在設(shè)備命名空間的\??目錄下生成一個(gè)名為”barf“的符號(hào)鏈接,該鏈接指向”“\\Device\\SECTEST_0“這個(gè)對(duì)象。 在核心態(tài)的驅(qū)動(dòng)程序中,我們需要調(diào)用以下的函數(shù)來(lái)創(chuàng)建相應(yīng)的符號(hào)鏈接: IoCreateSymbolicLink(linkname, targname); Linkname是要?jiǎng)?chuàng)建的符號(hào)鏈接名,相當(dāng)于上面函數(shù)中的”barf”,targname是該鏈接指向的設(shè)備對(duì)象。 如 果你創(chuàng)建了一個(gè)指向不存在的設(shè)備對(duì)象的符號(hào)鏈接,系統(tǒng)并不會(huì)作任何檢查,當(dāng)你訪問這個(gè)符號(hào)鏈接時(shí)只會(huì)收到一個(gè)錯(cuò)誤報(bào)告。所以你必須要自己保證鏈接的目的對(duì) 象真正存在。如果你想允許用戶模式程序能超越這個(gè)連接而轉(zhuǎn)到其它地方,應(yīng)使用IoCreateUnprotectedSymbolicLink函數(shù)替代上 面的IoCreateSymbolicLink函數(shù)。 給設(shè)備命名后我們就可以很方便地打開該設(shè)備進(jìn)行訪問了。但在方便的同時(shí)你需要注意一個(gè)很嚴(yán)重 的問題:“安全性”。一旦為設(shè)備命名后,符何核心態(tài)的驅(qū)動(dòng)程序都可以打開該設(shè)備的句柄,從而訪問此設(shè)備。而且更糟的是,任何用戶態(tài)的應(yīng)用程序也可以通過建 立該設(shè)備名的符號(hào)鏈接而訪問到該設(shè)備。而這種情況可能是你不愿意看到的。 一旦你決定要為你的設(shè)備命名時(shí),你應(yīng)該將這個(gè)設(shè)備對(duì)象的名稱放到對(duì)象名空間的“\Device”目錄中,我們可以使用以下的核心態(tài)函數(shù)來(lái)創(chuàng)建設(shè)備,同時(shí)給設(shè)備命名: - UNICODE_STRING devname;
- RtlInitUnicodeString(&devname, L"\\Device\\Simple0");
- IoCreateDevice(DriverObject, sizeof(DEVICE_EXTENSION), &devname, ...);
這 里的UNICODE_STRING devname就是用來(lái)存放設(shè)備名的地方。RtlInitUnicodeString是unicode串初始化函數(shù), 第一個(gè)參數(shù)是要初始化的變量地址,第二個(gè)為設(shè)備名常量。第二個(gè)參數(shù)前的大寫L是將這個(gè)常量轉(zhuǎn)換成此函數(shù)需要的寬字符串。一般我們使用如下的格式為設(shè)備命 名: 設(shè)備名0 其中的0為設(shè)備的實(shí)例號(hào)(即產(chǎn)生實(shí)例的順序)。 說(shuō)到這里我要提醒一下大家,在驅(qū)動(dòng)程序中一般不使用Ansi字符串,取而代之的是UniCode字符串,它以16位表示一個(gè)字符。這點(diǎn)和在WinCE下開發(fā)軟件很相似。 在 以前的老式驅(qū)動(dòng)程序中(Win 3.2 or Win95)中大量使用設(shè)備命名(包括直接用名字和名字的符號(hào)鏈接)的方式來(lái)訪問設(shè)備。這樣做有兩個(gè)很主要 的問題。一是安全性問題。在上面我們已經(jīng)講了這樣做有潛在的安全性問題,符何程序只要知道該設(shè)備的名字就可以訪問它。第二個(gè)問題是你的應(yīng)用程序要訪問該設(shè) 備必須事先知道它的名字,否則不能訪問。這在測(cè)試用的設(shè)備或私有設(shè)備(只為你的應(yīng)用程序服務(wù)而不向第三方提供接口)的情況下是可以的。但是如果你的硬件設(shè) 備還要為第三方的程序服務(wù)或者有可能有第三方的公司為你的設(shè)備寫驅(qū)動(dòng)時(shí)就會(huì)有很多問題??赡苣銓?duì)該設(shè)備的命名會(huì)和其它的設(shè)備相重復(fù)。而且這樣的命名很依賴 程序員本身所使用的自然語(yǔ)言。 為了解決這個(gè)問題,微軟在設(shè)計(jì)WDM框架時(shí)引入了一個(gè)新的命名方案。該方案與任何自然語(yǔ)言無(wú)關(guān),且易于擴(kuò)展,可廣泛 地用于軟件用硬件,并且易于歸檔。該方案依靠一個(gè)設(shè)備接口的概念。它基本上是軟件如何訪問硬件的一個(gè)說(shuō)明。一個(gè)設(shè)備接口由一個(gè)唯一的128位的GUID標(biāo) 識(shí)。一般情況下我們可以使用GUIDGEN工具生成這個(gè)標(biāo)識(shí)(GUIDGEN工具可以在VC++企業(yè)版的可執(zhí)行程序目錄下找到)。由于采用了獨(dú)特的生成算 法,你永遠(yuǎn)也不用擔(dān)心重復(fù)出現(xiàn)GUID的情況。這樣一個(gè)GUID就唯一標(biāo)識(shí)了一種設(shè)備接口。 生成的代碼如下所示 -
- DEFINE_GUID(<<name>>,
- 0xCAF53C68, 0xA94C, 0x11D2, 0xBB, 0x4A, 0x00, 0xC0, 0x4F, 0xA3, 0x30, 0xA6);
此為GUIDGEN程序工作時(shí)的截屏??梢赃x擇四種格式輸出。一般情況下我們選擇第二種。并且為了便于管理,我們把要用于的GUID聲明集中放到一個(gè)頭文件中。
你可以把設(shè)備接口想象成鎖和鑰匙。這樣應(yīng)用程序就可以準(zhǔn)確地訪問需要訪問的設(shè)備。 我們可以在功能驅(qū)動(dòng)程序的AddDevice例程序中注冊(cè)一個(gè)或多個(gè)設(shè)備接口,程序如下: - #include <initguid.h>
- #include "guids.h"
-
- NTSTATUS AddDevice(...)
- {
-
- IoRegisterDeviceInterface(pdo, &GUID_SIMPLE, NULL, &pdx->ifname);
-
- }
其中的GUID_SIPMLE就是我們要注冊(cè)的接口的GUID的定義。對(duì)此段代碼,我們作如下說(shuō)明: 我 們包含了GUIDS.H頭文件,那里定義了DEFINE_GUID宏。DEFINE_GUID通常聲明一個(gè)外部變量。在驅(qū)動(dòng)程序的某些地方,我們不得不為 將要引用的每個(gè)GUID保留初始化的存儲(chǔ)空間。系統(tǒng)頭文件INITGUID.H利用某些預(yù)編譯指令使DEFINE_GUID宏在已經(jīng)定義的情況下仍能保留 該存儲(chǔ)空間。 我使用單獨(dú)的頭文件來(lái)保存我要引用的GUID定義。這是一個(gè)好的想法,因?yàn)橛脩裟J降拇a也需要包含這些定義,但它們不需要那些僅與內(nèi)核模式驅(qū)動(dòng)程序有關(guān)的聲明。 IoRegisterDeviceInterface 的第一個(gè)參數(shù)必須是設(shè)備PDO的地址。第二個(gè)參數(shù)指出與接口關(guān)聯(lián)的GUID,第三個(gè)參數(shù)指出額外的接口細(xì)分類名。只有Microsoft的代碼才使用名稱 細(xì)分類方案。第四個(gè)參數(shù)是一個(gè)UNICODE_STRING串的地址,該串用于接收設(shè)備對(duì)象的符號(hào)連接名。 IoRegisterDeviceInterface 的返回值是一個(gè)Unicode 字符串,這樣可以在不知道驅(qū)動(dòng)程序的具體編碼的情況下(也就是說(shuō)沒看過你的驅(qū)動(dòng)程序的具體代碼),應(yīng)用程序可以確定并打開 該設(shè)備的句柄。這個(gè)返回值是很奇怪的,形如以下情形:\DosDevices\0000000000000007#{CAF53C68-A94C- 11d2-BB4A-00C04FA330A6}. 即它的名字是0000000000000007#{CAF53C68-A94C-11d2-BB4A-00C04FA330A6} 注冊(cè)過程實(shí)際上是先創(chuàng)建一個(gè)符號(hào)鏈接,然后把它記入注冊(cè)表。當(dāng)驅(qū)動(dòng)程序在響應(yīng)PnP請(qǐng)求IRP+MN_START_DEVICE時(shí),驅(qū)動(dòng)程序?qū)⒄{(diào)用IoSetDeviceInterfaceState函數(shù)”使能”該接口: IoSetDeviceInterfaceState(&pdx->ifname, TRUE); 所謂使能也就是使此符號(hào)鏈接指向具體的PDO對(duì)象。 在 響應(yīng)這個(gè)調(diào)用過程中,I/O管理器將創(chuàng)建一個(gè)指向設(shè)備PDO的符號(hào)連接對(duì)象。以后,驅(qū)動(dòng)程序會(huì)執(zhí)行一個(gè)功能相反的調(diào)用禁止該接口(用FALSE做參數(shù)調(diào)用 IoSetDeviceInterfaceState)。最后,I/O管理器刪除符號(hào)連接對(duì)象,但它保留了注冊(cè)表項(xiàng),即這個(gè)名字將總與設(shè)備的這個(gè)實(shí)例關(guān) 聯(lián);但符號(hào)連接對(duì)象與硬件一同到來(lái)或消失。 枚舉設(shè)備接口 內(nèi)核模式代碼和用戶模式代碼都能定位含有它們感興趣接口的設(shè)備。下面我將解釋如 何在用戶模式中枚舉所有含有特定接口的設(shè)備。枚舉代碼寫起來(lái)十分冗長(zhǎng),最后我不得不寫一個(gè)C++類來(lái)實(shí)現(xiàn)。你可以在DEVICELIST.CPP和 DEVICELIST.H文件中找到這些代碼。它們聲明并實(shí)現(xiàn)了一個(gè)CDeviceList類,該類包含一個(gè)CDeviceListEntry對(duì)象數(shù)組。 一些聲明代碼去掉了,詳細(xì)的文章請(qǐng)看驅(qū)動(dòng)開發(fā)網(wǎng)上的志寧專欄(http://www./column.php?sortid=3)。 所有實(shí)際的工作都發(fā)生在CDeviceList::Initialize函數(shù)中。其執(zhí)行過程大致是這樣:先枚舉所有接口GUID與構(gòu)造函數(shù)得到的GUID相同的設(shè)備,然后確定一個(gè)“友好”名,我們希望向最終用戶顯示這個(gè)名字。最后返回找到的設(shè)備號(hào)。下面是這個(gè)函數(shù)的代碼: - int CDeviceList::Initialize()
- {
- HDEVINFO info = SetupDiGetClassDevs(&m_guid, NULL, NULL
- , DIGCF_PRESENT | DIGCF_INTERFACEDEVICE);
- if (info == INVALID_HANDLE_VALUE)
- return 0;
- SP_INTERFACE_DEVICE_DATA ifdata;
- ifdata.cbSize = sizeof(ifdata);
- DWORD devindex;
- for (devindex = 0; SetupDiEnumDeviceInterfaces(info
- , NULL, &m_guid, devindex, &ifdata); ++devindex)
- {
- DWORD needed;
- SetupDiGetDeviceInterfaceDetail(info, &ifdata, NULL, 0, &needed, NULL);
-
- PSP_INTERFACE_DEVICE_DETAIL_DATA detail =
- (PSP_INTERFACE_DEVICE_DETAIL_DATA) malloc(needed);
- detail->cbSize = sizeof(SP_INTERFACE_DEVICE_DETAIL_DATA);
- SP_DEVINFO_DATA did = {sizeof(SP_DEVINFO_DATA)};
- SetupDiGetDeviceInterfaceDetail(info, &ifdata, detail, needed, NULL, &did));
- TCHAR fname[256];
- if (!SetupDiGetDeviceRegistryProperty(info,
- &did,
- SPDRP_FRIENDLYNAME,
- NULL,
- (PBYTE) fname,
- izeof(fname),
- NULL)
- && !SetupDiGetDeviceRegistryProperty(info,
- &did,
- SPDRP_DEVICEDESC,
- NULL,
- (PBYTE) fname,
- sizeof(fname),
- NULL)
- )
- _tcsncpy(fname, detail->DevicePath, 256);
- CDeviceListEntry e(detail->DevicePath, fname);
- free((PVOID) detail);
- m_list.Add(e);
- }
- SetupDiDestroyDeviceInfoList(info);
- return m_list.GetSize();
- }
該語(yǔ)句打開一個(gè)枚舉句柄,我們用它尋找包含了指定GUID接口的所有設(shè)備。 循環(huán)調(diào)用SetupDiEnumDeviceInterfaces以尋找每個(gè)與此相匹配的設(shè)備。 有兩項(xiàng)信息是我們需要的,接口的“細(xì)節(jié)”信息和設(shè)備實(shí)例信息。這個(gè)“細(xì)節(jié)”信息就是設(shè)備的符號(hào)名。因?yàn)樗拈L(zhǎng)度可變,所以我們兩次調(diào)用了SetupDiGetDeviceInterfaceDetail。第一次調(diào)用確定了長(zhǎng)度,第二次調(diào)用獲得了名字。 通過詢問注冊(cè)表中的FriendlyName鍵或DeviceDesc鍵,我們獲得了設(shè)備的“友好”名稱。 我們用設(shè)備符號(hào)名同時(shí)作為連接名和友好名創(chuàng)建了類CDeviceListEntry的一個(gè)臨時(shí)實(shí)例e。 友好名 你可能會(huì)疑惑,注冊(cè)表怎么會(huì)有設(shè)備的FriendlyName名。安裝設(shè)備驅(qū)動(dòng)程序的INF文件中有一個(gè)指定設(shè)備參數(shù)的段,這些參數(shù)將被添加到注冊(cè)表中。通常我們可以在這里為設(shè)備提供一個(gè)FriendlyName名。 注:在windows 2k下和Windows 98下的inf文件有少許的不同,即使是同一個(gè)設(shè)備的inf文件,也要作過適當(dāng)修改后才能同時(shí)用于兩個(gè)平臺(tái)下。 作好以上工作后,我們還要初始化一些其它的數(shù)據(jù)結(jié)構(gòu)才能完成設(shè)備加載工作。 在AddDevice中還需要加入其它一些步驟來(lái)初始化設(shè)備對(duì)象,下面我將按順序描述這些步驟。 設(shè)備擴(kuò)展的內(nèi)容和管理全部由用戶決定。該結(jié)構(gòu)中的數(shù)據(jù)成員應(yīng)直接反映硬件的專有細(xì)節(jié)以及對(duì)設(shè)備的編程方式。大多數(shù)驅(qū)動(dòng)程序都會(huì)在這里放入一些數(shù)據(jù)項(xiàng),下面代碼聲明了一個(gè)設(shè)備擴(kuò)展結(jié)構(gòu): - typedef struct _DEVICE_EXTENSION {
- PDEVICE_OBJECT DeviceObject;
- PDEVICE_OBJECT LowerDeviceObject;
- PDEVICE_OBJECT Pdo;
- UNICODE_STRING ifname;
- IO_REMOVE_LOCK RemoveLock;
- DEVSTATE devstate;
- DEVSTATE prevstate;
- DEVICE_POWER_STATE devpower;
- SYSTEM_POWER_STATE syspower;
- DEVICE_CAPABILITIES devcaps;
- ...
- } DEVICE_EXTENSION, *PDEVICE_EXTENSION;
我模仿DDK中官方的結(jié)構(gòu)聲明模式聲明了這個(gè)結(jié)構(gòu)。 我 們可以用設(shè)備對(duì)象中的DeviceExtension指針定位自己的設(shè)備擴(kuò)展。同樣,我們有時(shí)也需要在給定設(shè)備擴(kuò)展時(shí)能定位設(shè)備對(duì)象。因?yàn)槟承┖瘮?shù)的邏輯 參數(shù)就是設(shè)備擴(kuò)展本身(這里有設(shè)備每個(gè)實(shí)例的全部信息)。所以,我認(rèn)為這里應(yīng)該有一個(gè)DeviceObject指針。 我在一些地方曾提到過,在調(diào)用IoAttachDeviceToDeviceStack函數(shù)時(shí),應(yīng)該把緊接著你下面的設(shè)備對(duì)象的地址保存起來(lái)。LowerDeviceObject成員用于保存這個(gè)地址。 有一些服務(wù)例程需要PDO的地址,而不是堆棧中某個(gè)高層設(shè)備對(duì)象的地址。由于定位PDO非常困難,所以最好的辦法是在AddDevice執(zhí)行時(shí)在設(shè)備擴(kuò)展中保存一個(gè)PDO地址。 無(wú)論你用什么方法(符號(hào)連接或設(shè)備接口)命名你的設(shè)備,都希望能容易地獲得這個(gè)名字。所以,這里我用一個(gè)Unicode串成員ifname來(lái)保存設(shè)備接口名。如果你使用一個(gè)符號(hào)連接名而不是設(shè)備接口,應(yīng)該使用一個(gè)有相關(guān)含義的成員名,例如“l(fā)inkname”。 當(dāng)你調(diào)用IoDeleteDevice刪除這個(gè)設(shè)備對(duì)象時(shí),需要使用一個(gè)自旋鎖來(lái)解決同步安全問題,我將在第六章中討論同步問題。因此,需要在設(shè)備擴(kuò)展中分配一個(gè)IO_REMOVE_LOCK對(duì)象。AddDevice有責(zé)任初始化這個(gè)對(duì)象。 你可能需要一個(gè)成員來(lái)記錄設(shè)備當(dāng)前的PnP狀態(tài)和電源狀態(tài)。DEVSTATE和POWERSTATE是枚舉類型變量,我假設(shè)事先已經(jīng)在頭文件中聲明了這些變量類型。我將在后面章節(jié)中討論這些狀態(tài)變量的用途。 電源管理的另一個(gè)部分涉及電源能力設(shè)置的恢復(fù),設(shè)備擴(kuò)展中的devcaps結(jié)構(gòu)用于保存這些設(shè)置。 下面是AddDevice中的初始化語(yǔ)句(著重設(shè)備擴(kuò)展部分的初始化): - NTSTATUS AddDevice(...)
- {
- PDEVICE_OBJECT fdo;
- IoCreateDevice(..., sizeof(DEVICE_EXTENSION), ..., &fdo);
- PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION) fdo->DeviceExtension;
- pdx->DeviceObject = fdo;
- pdx->Pdo = pdo;
- IoInitializeRemoveLock(&pdx->RemoveLock, ...);
- pdx->devstate = STOPPED;
- pdx->devpower = PowerDeviceD0;
- pdx->syspower = PowerSystemWorking;
- IoRegisterDeviceInterface(..., &pdx->ifname);
- pdx->LowerDeviceObject = IoAttachDeviceToDeviceStack(...);
- }
初始化默認(rèn)的DPC對(duì)象 許 多設(shè)備使用中斷來(lái)完成操作。中斷服務(wù)例程(ISR)不能調(diào)用用于報(bào)告IRP完成的函數(shù)(IoCompleteRequest)。利用DPC(延遲過程調(diào) 用)可以避開這個(gè)限制。你的設(shè)備對(duì)象中應(yīng)包含一個(gè)輔助DPC對(duì)象,它可以調(diào)度你的DPC例程,該對(duì)象應(yīng)該在設(shè)備對(duì)象創(chuàng)建后不久被初始化。DPC例程還有其 它的作用,比如你需要在中斷中處理很多很耗時(shí)的操作,這在通常情況下是不可以的,這樣會(huì)影響操作系統(tǒng)的響應(yīng)速度,為此我們把這些處理操作移到DPC例程中去。 NTSTATUS AddDevice(...) { IoCreateDevice(...); IoInitializeDpcRequest(fdo, DpcForIsr); } 設(shè)置緩沖區(qū)對(duì)齊掩碼 執(zhí) 行DMA傳輸?shù)脑O(shè)備直接使用內(nèi)存中的數(shù)據(jù)緩沖區(qū)工作。HAL(硬件抽象層)要求DMA傳輸中使用的緩沖區(qū)必須按某個(gè)特定界限對(duì)齊,而且設(shè)備也可能有更嚴(yán)格 的對(duì)齊需求。設(shè)備對(duì)象中的AlignmentRequirement域表達(dá)了這個(gè)約束,它是一個(gè)位掩碼,等于要求的地址邊界減一。下面語(yǔ)句可以把任何地址 圈入這個(gè)界限: PVOID address = ...; SIZE_T ar = fdo->AlignmentRequirement; address = (PVOID) ((SIZE_T) address & ~ar); 還可以把任意地址圈入下一個(gè)對(duì)齊邊界: PVOID address = ...; SIZE_T ar = fdo->AlignmentRequirement; address = (PVOID) (((SIZE_T) address + ar) & ~ar); 在這兩段代碼中,我使用了SIZE_T把指針類型(它可以是32位也可以是64位,這取決于編譯的目標(biāo)平臺(tái))轉(zhuǎn)化成一個(gè)整型,該整型與原指針有同樣的跨度范圍。 IoCreateDevice 把新設(shè)備對(duì)象中的AlignmentRequirement域設(shè)置成HAL要求的值。例如,Intel的x86芯片沒有對(duì)齊需求,所以 AlignmentRequirement的默認(rèn)值為0。如果設(shè)備需要更嚴(yán)格的緩沖區(qū)對(duì)齊(例如設(shè)備有總線主控的DMA能力,要求對(duì)齊數(shù)據(jù)緩沖區(qū)),應(yīng)該 修改這個(gè)默認(rèn)值,如下: if (MYDEVICE_ALIGNMENT - 1 > fdo->AlignmentRequirement) fdo->AlignmentRequirement = MYDEVICE_ALIGNMENT - 1; 我假設(shè)你在驅(qū)動(dòng)程序某處已定義了一個(gè)名為MYDEVICE_ALIGNMENT的常量,它是2的冪,代表設(shè)備的數(shù)據(jù)緩沖區(qū)對(duì)齊需求。 其它對(duì)象 設(shè)備可能還有其它一些需要在AddDevice中初始化的對(duì)象。這些對(duì)象可能包括各種同步對(duì)象,各種隊(duì)列頭(queue anchors),聚集/分散列表緩沖區(qū),等等。 初始化設(shè)備標(biāo)志 設(shè) 備對(duì)象中有兩個(gè)標(biāo)志位需要在AddDevice中初始化,并且它們?cè)谝院笠膊粫?huì)改變,它們是DO_BUFFERED_IO和DO_DIRECT_IO標(biāo) 志。你只能設(shè)置并使用其中一個(gè)標(biāo)志,它將決定你以何種方式處理來(lái)自用戶模式的內(nèi)存緩沖區(qū)。(我將在第七章中討論這兩種緩沖模式的不同,以及你如何選 擇) 由于任何在后面裝入的上層過濾器驅(qū)動(dòng)程序?qū)?fù)制你的標(biāo)志設(shè)置,所以在AddDevice中做這個(gè)選擇十分重要。如果你在過濾器驅(qū)動(dòng)程序裝入后改變了 設(shè)置,它們可能會(huì)不知道。 設(shè)備對(duì)象中有兩個(gè)標(biāo)志位屬于電源管理范疇。與前兩個(gè)緩沖區(qū)標(biāo)志不同,這兩個(gè)標(biāo)志在任何時(shí)間都可以被改變。我將在第八章中 詳細(xì)討論它們,但這里我先介紹一下。DO_POWER_PAGABLE意味著電源管理器將在PASSIVE_LEVEL級(jí)上向你發(fā)送 IRP_MJ_POWER請(qǐng)求。DO_POWER_INRUSH意味著你的設(shè)備在上電時(shí)將汲取大量電流,因此,電源管理器將確保沒有其它INRUSH設(shè)備 同時(shí)上電。 設(shè)置初始電源狀態(tài) 大部分設(shè)備一開始就進(jìn)入全供電狀態(tài)。如果你知道你的設(shè)備的初始電源狀態(tài),應(yīng)該告訴電源管理器: POWER_STATE state; state.DeviceState = PowerDeviceD0; PoSetPowerState(fdo, DevicePowerState, state) 建立設(shè)備堆 每個(gè)過濾器驅(qū)動(dòng)程序和功能驅(qū)動(dòng)程序都有責(zé)任把自己的設(shè)備對(duì)象放到設(shè)備堆棧上,從PDO開始一直向上。你可以調(diào)用IoAttachDeviceToDeviceStack完成你那部分工作: NTSTATUS AddDevice(..., PDEVICE_OBJECT pdo) { PDEVICE_OBJECT fdo; IoCreateDevice(..., &fdo); pdx->LowerDeviceObject = IoAttachDeviceToDeviceStack(fdo, pdo); } IoAttachDeviceToDeviceStack 的第一個(gè)參數(shù)是新創(chuàng)建的設(shè)備對(duì)象的地址。第二個(gè)參數(shù)是PDO地址。AddDevice的第二個(gè)參數(shù)也是這個(gè)地址。返回值是緊接著你下面的任何設(shè)備對(duì)象的地 址,它可以是PDO,也可以是其它低級(jí)過濾器設(shè)備對(duì)象。如果該函數(shù)失敗則返回一個(gè)NULL指針,因此你的AddDevice函數(shù)也是失敗的,應(yīng)返回 STATUS_DEVICE_REMOVED。 在AddDevice中最后一件需要做的事是清除設(shè)備對(duì)象中的DO_DEVICE_INITIALIZING標(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)志。在以 前版本的Windows NT中,大部分驅(qū)動(dòng)程序在DriverEntry中創(chuàng)建所有需要的設(shè)備對(duì)象。當(dāng)DriverEntry返回時(shí),I/O管理器自動(dòng) 遍歷設(shè)備對(duì)象列表并清除該標(biāo)志。但在WDM驅(qū)動(dòng)程序中,設(shè)備對(duì)象在DriverEntry返回后才創(chuàng)建,所以I/O管理器不會(huì)自動(dòng)清除這個(gè)標(biāo)志,驅(qū)動(dòng)程序 必須自己清除它。 這是本節(jié)的內(nèi)容。 |