• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>

            小默

            [zz]AddDevice

            在前一節(jié)中,我講述了當(dāng)WDM驅(qū)動程序被第一次裝入時如何初始化。通常,一個驅(qū)動程序可以被多個設(shè)備利用。WDM驅(qū)動程序有一個特殊的AddDevice函數(shù),PnP管理器為每個設(shè)備實例調(diào)用該函數(shù)。該函數(shù)的原型如下:

            NTSTATUS AddDevice(PDRIVER_OBJECT DriverObject, PDEVICE_OBJECT pdo)
                        {
                        }

            DriverObject參數(shù)指向一個驅(qū)動程序?qū)ο?/strong>,就是你在DriverEntry例程中初始化的那個驅(qū)動程序?qū)ο蟆?strong>pdo參數(shù)指向設(shè)備堆棧底部物理設(shè)備對象

            對于功能驅(qū)動程序,其AddDevice函數(shù)的基本職責(zé)是創(chuàng)建一個設(shè)備對象并把它連接到以pdo為底的設(shè)備堆棧中。相關(guān)步驟如下:

            1. 調(diào)用IoCreateDevice創(chuàng)建設(shè)備對象,并建立一個私有的設(shè)備擴展對象
            2. 寄存一個或多個設(shè)備接口,以便應(yīng)用程序能知道設(shè)備的存在。另外,還可以給出設(shè)備名并創(chuàng)建符號連接
            3. 初始化設(shè)備擴展和設(shè)備對象的Flag成員。
            4. 調(diào)用IoAttachDeviceToDeviceStack函數(shù)把新設(shè)備對象放到堆棧上

            下面我將詳細解釋這些步驟。

            創(chuàng)建設(shè)備對象

            調(diào)用IoCreateDevice函數(shù)創(chuàng)建設(shè)備對象,例如:

            PDEVICE_OBJECT fdo;
                        NTSTATUS status = IoCreateDevice(DriverObject,
                        sizeof(DEVICE_EXTENSION),
                        NULL,
                        FILE_DEVICE_UNKNOWN,
                        FILE_DEVICE_SECURE_OPEN,
                        FALSE,
                        &fdo);

            第一個參數(shù)(DriverObject) 就是AddDevice的第一個參數(shù)。該參數(shù)用于在驅(qū)動程序和新設(shè)備對象之間建立連接,這樣I/O管理器就可以向設(shè)備發(fā)送指定的IRP。

            第二個參數(shù)是設(shè)備擴展結(jié)構(gòu)的大小。正如我在本章前面講到的,I/O管理器自動分配這個內(nèi)存,并把設(shè)備對象中的DeviceExtension指針指向這塊內(nèi)存。

            第三個參數(shù)在本例中為NULL。它可以是命名該設(shè)備對象的UNICODE_STRING串的地址。決定是否命名設(shè)備對象以及以什么名字命名還需要仔細考慮,我將在本節(jié)后面深入討論這個問題。

            第四個參數(shù)(FILE_DEVICE_UNKNOWN) 是表2-4中列出的設(shè)備類型。這個值可以被設(shè)備硬件鍵或類鍵中的超越值所替代,如果這兩個鍵都含有該參數(shù)的超越值,那么硬件鍵中的超越值具有更高的優(yōu)先權(quán)。對于屬于某個已存在類的設(shè)備,必須在這些地方指定正確的值,因為驅(qū)動程序與外圍系統(tǒng)的交互需要依靠這個值。另外,設(shè)備對象的默認安全設(shè)置也依靠這個設(shè)備類型值。

            第五個參數(shù)(FILE_DEVICE_SECURE_OPEN) 為設(shè)備對象提供Characteristics標志(見表2-3)。這些標志主要關(guān)系到塊存儲設(shè)備(如軟盤、CDROM、Jaz等等)。未公開標志位FILE_AUTOGENERATED_DEVICE_NAME僅用于內(nèi)部使用,并不是DDK文檔忘記提到該標志。這個參數(shù)同樣也能被硬件鍵或類鍵中的對應(yīng)值超越,如果兩個值都存在,那么硬件鍵中的超越值具有更高的優(yōu)先權(quán)。

            第六個參數(shù)(FALSE) 指出設(shè)備是否是排斥的。通常,對于排斥設(shè)備,I/O管理器僅允許打開該設(shè)備的一個句柄。這個值同樣也能被注冊表中硬件鍵和類鍵中的值超越,如果兩個超越值都存在,硬件鍵中的超越值具有更高的優(yōu)先權(quán)。

            注意
            排斥屬性僅關(guān)系到打開請求的目標是命名設(shè)備對象。如果你遵守Microsoft推薦的WDM驅(qū)動程序設(shè)計方針,沒有為設(shè)備對象命名,那么打開請求將直接指向PDO。PDO通常不能被標記為排斥,因為總線驅(qū)動程序沒有辦法知道設(shè)備是否需要排斥特征。把PDO標為排斥的唯一的機會在注冊表中,即設(shè)備硬件鍵或類鍵的Properties子鍵含有Exclusive超越值。為了完全避免依賴排斥屬性,你應(yīng)該利用IRP_MJ_CREAT例程彈出任何有違規(guī)行為的打開請求。

            第七個參數(shù)(&fdo) 是存放設(shè)備對象指針的地址,IoCreateDevice函數(shù)使用該變量保存剛創(chuàng)建設(shè)備對象的地址

            如果IoCreateDevice由于某種原因失敗,則它返回一個錯誤代碼,不改變fdo中的值。如果IoCreateDevice函數(shù)返回成功代碼,那么它同時也設(shè)置了fdo指針。然后我們進行到下一步,初始化設(shè)備擴展,做與創(chuàng)建新設(shè)備對象相關(guān)的其它工作,如果在這之后又發(fā)現(xiàn)了錯誤,那么在返回前應(yīng)先釋放剛創(chuàng)建的設(shè)備對象并返回狀態(tài)碼。見下面例子代碼:

            NTSTATUS status = IoCreateDevice(...);
                        if (!NT_SUCCESS(status))
                        return status;
                        ...
                        if (<some other error discovered>)
                        {
                        IoDeleteDevice(fdo);
                        return status;
                        }

            NTSTATUS狀態(tài)代碼和NT_SUCCESS宏的解釋見下一章。

            為設(shè)備命名

            Windows NT使用對象管理器集中管理大量的內(nèi)部數(shù)據(jù)結(jié)構(gòu),包括我們討論過的驅(qū)動程序?qū)ο蠛驮O(shè)備對象。David Solomon在《Inside Windows NT, Second Edition (Microsoft Press, 1998)》的第三章“System Mechanisms”中給出了關(guān)于Windows NT對象管理器和命名空間的一個比較完整的闡述。對象都有名稱,對象管理器用一個層次化的命名空間來管理這些名稱。圖2-16是DevView顯示的頂層對象名。圖中以文件夾形式顯示的對象是目錄對象,它可以包含子目錄或常規(guī)對象,其它圖標則代表正常對象。(從這一點上看,DevView與平臺SDK中的WINOBJ工具相類似,但WINOBJ不能給出設(shè)備對象和驅(qū)動程序的相關(guān)信息)

            圖2-16. 用DevView觀察命名空間

            通常設(shè)備對象都把自己的名字放到\Device目錄中。在Windows 2000中,設(shè)備的名稱有兩個用途。第一個用途,設(shè)備命名后,其它內(nèi)核模式部件可以通過調(diào)用IoGetDeviceObjectPointer函數(shù)找到該設(shè)備,找到設(shè)備對象后,就可以向該設(shè)備的驅(qū)動程序發(fā)送IRP

            另一個用途,允許應(yīng)用程序打開命名設(shè)備的句柄,這樣它們就可以向驅(qū)動程序發(fā)送IRP。應(yīng)用程序可以使用標準的CreateFile API打開命名設(shè)備句柄,然后用ReadFileWriteFile,和DeviceIoControl向驅(qū)動程序發(fā)出請求。應(yīng)用程序打開設(shè)備句柄時使用\\.\路徑前綴而不是標準的UNC(統(tǒng)一命名約定)名稱,如C:\MYFILE.CPP或\\FRED\C-Drive\HISFILE.CPP。在內(nèi)部,I/O管理器在執(zhí)行名稱搜索前自動把\\.\轉(zhuǎn)換成\??\。為了把\??目錄中的名字與名字在其它目錄(例如,在\Device目錄)中的對象相連接,對象管理器實現(xiàn)了一種稱為符號連接(symbolic link)的對象。

            符號連接

            符號連接有點象桌面上的快捷方式,符號連接在Windows NT中的主要用途是把處于列表前面的DOS形式的名稱連接到設(shè)備上。圖2-17顯示了\??目錄的部分內(nèi)容,這里就有一些符號名,例如,“C:”和其它一些用DOS命名方案命名的驅(qū)動器名稱,它們被連接到\Device目錄中,而這些設(shè)備對象的真正名稱就放在\Device目錄中。符號連接可以使對象管理器在分析一個名稱時能跳到命名空間的某個地方。例如,如果我用CreateFile打開名稱為“C:\MYFILE.CPP”的對象,對象管理器將以下面過程打開該文件:

            1. 內(nèi)核模式代碼最開始看到的名稱是\??\C:\MYFILE.CPP對象管理器根目錄中查找“??”。
            2. 找到\??目錄后,對象管理器在其中查找“C:”。它發(fā)現(xiàn)找到的對象是一個符號連接,所以它就用這個符號連接組成一個新的內(nèi)核模式路徑名:\Device\HarddiskVolume1\MYFILE.CPP,然后析取它。
            3. 使用新路徑名后,對象管理器重新在根目錄中查找“Device”。
            4. 找到\Device目錄后,對象管理器在其中查找“HarddiskVolume1”,最后它找到一個以該名字命名的設(shè)備。

            圖2-17. \??目錄和部分符號連接

            現(xiàn)在,對象管理器創(chuàng)建一個IRP,然后把它發(fā)到HarddiskVolume1設(shè)備的驅(qū)動程序。該IRP最終將使某個文件系統(tǒng)驅(qū)動程序或其它驅(qū)動程序定位并打開一個磁盤文件。描述文件系統(tǒng)驅(qū)動程序的工作過程已經(jīng)超出了本書的范圍。如果我們使用設(shè)備名COM1,那么最終收到該IRP的將是\Device\Serial0的驅(qū)動程序。

            用戶模式程序可以調(diào)用DefineDosDevice創(chuàng)建一個符號連接,如下例:

            BOOL okay = DefineDosDevice(DDD_RAW_TARGET_PATH, "barf", "\\Device\\SECTEST_0");

            圖2-17中顯示了上面調(diào)用的結(jié)果。

            如果你需要在WDM驅(qū)動程序中創(chuàng)建一個符號連接,可以調(diào)用IoCreateSymbolicLink函數(shù):

            IoCreateSymbolicLink(linkname, targname);

            linkname是要創(chuàng)建的符號連接名,targname是要連接的名字。順便說一下,對象管理器并不關(guān)心targname是否是已存在對象的名字,如果連接到一個未定義的符號名,那么訪問該符號連接將簡單地收到一個錯誤。如果你想允許用戶模式程序能超越這個連接而轉(zhuǎn)到其它地方,應(yīng)使用IoCreateUnprotectedSymbolicLink函數(shù)替代上面函數(shù)。

            應(yīng)該命名設(shè)備對象嗎?

            決定為設(shè)備對象命名之前,你應(yīng)該多想一想。如果命名了設(shè)備對象,那么任何內(nèi)核模式程序都可以打開該設(shè)備的句柄。另外,任何內(nèi)核模式或用戶模式程序都能創(chuàng)建連接到該設(shè)備的符號連接,并可以使用這個符號連接打開設(shè)備的句柄。你可能允許也可能不允許這種事情發(fā)生。

            是否命名設(shè)備對象的主要考慮是安全問題。當(dāng)有人打開一個命名對象的句柄時,對象管理器將檢查他是否有權(quán)這樣做。當(dāng)IoCreateDevice為你創(chuàng)建設(shè)備對象時,它也為設(shè)備對象設(shè)置了一個默認安全描述符(基于第四個參數(shù)中的設(shè)備類型)。下面是三個基本分類,I/O管理器基于這些分類來選擇安全描述符。(參考表2-4中的第二列)

            • 大部分文件系統(tǒng)設(shè)備對象(磁盤、CD-ROM、文件、磁帶)將得到“public default unrestrictedACL(訪問控制表)。該表對系統(tǒng)(SYSTEM)和管理員(administrator)之外的所有賬戶給予了SYNCHRONIZE、READ_CONTROL、FILE_READ_ATTRIBUTES、FILE_TRAVERSE訪問權(quán)限。順便說一下,文件系統(tǒng)設(shè)備對象就是作為CreateFile函數(shù)的目標而存在,CreateFile函數(shù)將打開一個由文件系統(tǒng)管理的文件。
            • 磁盤設(shè)備和網(wǎng)絡(luò)文件系統(tǒng)對象將得到與文件系統(tǒng)對象相同的ACL,但做了一些修改。例如,任何人對命名軟磁盤設(shè)備對象都有全部訪問權(quán)管理員有足夠的權(quán)限運行ScanDisk。(用戶模式的網(wǎng)絡(luò)支持DLL需要更大的權(quán)限來訪問其對應(yīng)文件系統(tǒng)驅(qū)動程序的設(shè)備對象,這就是網(wǎng)絡(luò)文件系統(tǒng)需要與其它文件系統(tǒng)區(qū)別對待的原因)
            • 所有其它的設(shè)備對象將得到“public open unrestricted”ACL,它允許任何有設(shè)備句柄的人不受限制地使用該設(shè)備。

            可以看出,如果非磁盤設(shè)備的驅(qū)動程序在調(diào)用IoCreateDevice時給出設(shè)備對象名,那么任何人都可以讀寫這個設(shè)備,因為默認安全設(shè)置幾乎允許用戶有全部的訪問權(quán)限,而且在創(chuàng)建符號連接時根本不進行安全檢查。安全檢查僅發(fā)生在對設(shè)備的打開操作上,基于命名對象的安全描述符。這對于在同一堆棧中的有更嚴格安全限制的其它設(shè)備對象也是這樣。

            DevView可以顯示設(shè)備對象的安全屬性。你可以通過測試一個文件系統(tǒng)、一個磁盤設(shè)備、或者任何其它隨機存取設(shè)備了解到我剛描述過的默認操作規(guī)則。

            PDO也得到一個默認安全描述符,但這個安全描述符可能被存儲在硬件鍵或類鍵的Properties子鍵中的安全描述符超越(當(dāng)兩者都存在時,硬件鍵中的超越值有更高的優(yōu)先權(quán))。即使沒有指定安全描述符超越,如果硬件鍵或類鍵的Properties子鍵中有設(shè)備類型或特征的超越值,那么I/O管理器也會基于新類型為對象構(gòu)造一個新的默認安全描述符。但I/O管理器不會超越PDO上面的任何其它設(shè)備對象的安全設(shè)置。因此,由于超越的影響,你不應(yīng)該命名你的設(shè)備對象。但不要失望,應(yīng)用程序仍可以使用注冊的接口(interface)訪問你的設(shè)備。

            關(guān)于安全問題的最后一點:當(dāng)對象管理器析取對象名時,對于名字的中間部分僅需要具有FILE_TRAVERSE訪問權(quán),它僅在最終對象名上執(zhí)行全部的安全檢查。所以,假設(shè)某個設(shè)備對象可以通過\Device\SECTEST_0名或符號連接\??\SecurityTest_0名到達,那么,如果設(shè)備對象的安全描述符設(shè)置為拒絕寫,則試圖以寫方式打開\\.\SecurityTest_0的用戶模式應(yīng)用程序?qū)⒈蛔枞5绻麘?yīng)用程序試圖打開名為\\.\SecurityTest_0\ExtraStuff的對象,那么打開請求(IRP_MJ_CREATE形式)將被允許,而此時用戶對\\.\SecurityTest_0\僅有FILE_TRAVERSE權(quán)限。I/O管理器希望設(shè)備驅(qū)動程序自己去處理額外名稱部件的安全檢查。

            為了避免涉及到我剛描述過的安全問題,你可以在調(diào)用IoCreateDevice時指定設(shè)備特征參數(shù)為FILE_DEVICE_SECURE_OPEN。該標志將使Windows 2000在額外名稱部件存在的情況下仍檢查調(diào)用者是否有權(quán)限打開設(shè)備句柄。

            設(shè)備名稱

            如果你決定命名設(shè)備對象,通常應(yīng)該把對象名放在名稱空間的\Device分支中。為了命名設(shè)備對象,首先應(yīng)該創(chuàng)建一個UNICODE_STRING結(jié)構(gòu)存放對象名,然后把該串作為調(diào)用IoCreateDevice的參數(shù)

            UNICODE_STRING devname;
                        RtlInitUnicodeString(&devname, L"\\Device\\Simple0");
                        IoCreateDevice(DriverObject, sizeof(DEVICE_EXTENSION), &devname, ...);

            我將在下一章中討論RtlInitUnicodeString的用法。

            通常,驅(qū)動程序用設(shè)備類型串后加上一個以0開始的實例號作為設(shè)備對象名(如上面的Simple0)。一般,你不希望象我上面做的那樣使用帶有硬編碼性質(zhì)的名稱。你希望用串操作函數(shù)動態(tài)地合成一個名字:

            UNICODE_STRING devname;
                        static LONG lastindex = -1;
                        LONG devindex = InterlockedIncrement(&lastindex);
                        WCHAR name[32];
                        _snwprintf(name, arraysize(name), L"\\Device\\SIMPLE%2.2d", devindex);
                        RtlInitUnicodeString(&devname, name);
                        IoCreateDevice(...);

            我將在后兩章中解釋上面代碼中出現(xiàn)的服務(wù)函數(shù)。如上面代碼所示,從私有設(shè)備類型得出的實例號應(yīng)該是一個靜態(tài)變量

            設(shè)備接口

            用舊的命名方法命名設(shè)備對象,并創(chuàng)建一個應(yīng)用程序能夠使用的符號連接,存在著兩個主要問題。命名設(shè)備對象所帶來的潛在安全問題我們已經(jīng)討論過。此外,訪問設(shè)備的應(yīng)用程序需要先知道設(shè)備采用的命名方案。如果你的硬件僅由你的應(yīng)用程序訪問,那么不會有什么問題。但是,如果有其它公司想為你的硬件寫應(yīng)用程序,并且有許多硬件公司想制作相似的設(shè)備,那么設(shè)計一個合適的命名方案是困難的。最后,許多命名方案將依賴于程序員所說的自然語言,這不是一個好的選擇。

            為了解決這些問題,WDM引入了一個新的設(shè)備命名方案,該方案是語言中立的、易于擴展的、可用于許多硬件和軟件廠商,并且易于文檔化。該方案依靠一個設(shè)備接口(device interface)的概念,它基本上是軟件如何訪問硬件的一個說明。一個設(shè)備接口被一個128位的GUID唯一標識。你可以用平臺SDK中的UUIDGEN工具或者GUIDGEN工具生成GUID,這兩個工具輸出同一種數(shù),但格式不同。這個想法就象某些工業(yè)組織聯(lián)合起來共同制定某種硬件的標準訪問方法一樣。在標準制作過程中,產(chǎn)生了一些GUID,這些GUID將永遠關(guān)聯(lián)到某些接口上。

            圖2-18. 使用GUIDGEN生成GUID

            我想接口類似于蛋白質(zhì)合成器,它能制作活細胞的細胞膜。訪問特定種類設(shè)備的應(yīng)用程序有自己的蛋白質(zhì)合成器,它就象一把鑰匙,可以插入到所有有匹配合成器的設(shè)備驅(qū)動程序中。如圖2-19。

            圖2-19. 用設(shè)備接口匹配應(yīng)用程序和設(shè)備

            注冊設(shè)備接口 調(diào)用IoRegisterDeviceInterface函數(shù),功能驅(qū)動程序的AddDevice函數(shù)可以注冊一個或多個設(shè)備接口:

            #include <initguid.h>							<--1
                        #include "guids.h"							<--2
                        ...
                        NTSTATUS AddDevice(...)
                        {
                        ...
                        IoRegisterDeviceInterface(pdo, &GUID_SIMPLE, NULL, &pdx->ifname);	<--3
                        ...
                        }
            1. 我們包含了GUIDS.H頭文件,那里定義了DEFINE_GUID宏。DEFINE_GUID通常聲明一個外部變量。在驅(qū)動程序的某些地方,我們不得不為將要引用的每個GUID保留初始化的存儲空間。系統(tǒng)頭文件INITGUID.H利用某些預(yù)編譯指令使DEFINE_GUID宏在已經(jīng)定義的情況下仍能保留該存儲空間。
            2. 我使用單獨的頭文件來保存我要引用的GUID定義。這是一個好的想法,因為用戶模式的代碼也需要包含這些定義,但它們不需要那些僅與內(nèi)核模式驅(qū)動程序有關(guān)的聲明。
            3. IoRegisterDeviceInterface的第一個參數(shù)必須是設(shè)備PDO的地址。第二個參數(shù)指出與接口關(guān)聯(lián)的GUID,第三個參數(shù)指出額外的接口細分類名。只有Microsoft的代碼才使用名稱細分類方案。第四個參數(shù)是一個UNICODE_STRING串的地址,該串用于接收設(shè)備對象的符號連接名。

            IoRegisterDeviceInterface的返回值是一個Unicode串,這樣在不知道驅(qū)動程序編碼的情況下,應(yīng)用程序能用該串確定并打開設(shè)備句柄。順便說一下,這個名字比較丑陋;后面例子是我在Windows 98中為Sample設(shè)備生成的名字:\DosDevices\0000000000000007#{CAF53C68-A94C-11d2-BB4A-00C04FA330A6}。

            注冊過程實際就是先創(chuàng)建一個符號連接名,然后再把它存入注冊表。之后,當(dāng)響應(yīng)PnP請求IRP_MN_START_DEVICE時,驅(qū)動程序?qū)⒄{(diào)用IoSetDeviceInterfaceState函數(shù)“使能”該接口:

            IoSetDeviceInterfaceState(&pdx->ifname, TRUE);

            在響應(yīng)這個調(diào)用過程中,I/O管理器將創(chuàng)建一個指向設(shè)備PDO的符號連接對象。以后,驅(qū)動程序會執(zhí)行一個功能相反的調(diào)用禁止該接口(用FALSE做參數(shù)調(diào)用IoSetDeviceInterfaceState)。最后,I/O管理器刪除符號連接對象,但它保留了注冊表項,即這個名字將總與設(shè)備的這個實例關(guān)聯(lián);但符號連接對象與硬件一同到來或消失。

            因為接口名最終指向PDO,所以PDO的安全描述符將最終控制設(shè)備的訪問權(quán)限。這樣比較好,因為只有管理員才可以通過控制臺控制PDO的安全屬性。

            枚舉設(shè)備接口 內(nèi)核模式代碼和用戶模式代碼都能定位含有支持它們感興趣接口的設(shè)備。下面我將解釋如何在用戶模式中枚舉所有含有特定接口的設(shè)備。枚舉代碼寫起來十分冗長,最后我不得不寫一個C++類來實現(xiàn)。你可以在DEVICELIST.CPP和DEVICELIST.H文件中找到這些代碼,這些文件是第八章“電源管理”中WDMIDLE例子的一部分。它們聲明并實現(xiàn)了一個CDeviceList類,該類包含一個CDeviceListEntry對象數(shù)組。這兩個類聲明如下:

            class CDeviceListEntry
                        {
                        public:
                        CDeviceListEntry(LPCTSTR linkname, LPCTSTR friendlyname);
                        CDeviceListEntry(){}
                        CString m_linkname;
                        CString m_friendlyname;
                        };
                        class CDeviceList
                        {
                        public:
                        CDeviceList(const GUID& guid);
                        ~CDeviceList();
                        GUID m_guid;
                        CArray<CDeviceListEntry, CDeviceListEntry&> m_list;
                        int Initialize();
                        };

            該類使用了CString類和CArray模板,它們都是MFC的一部分。這兩個類的構(gòu)造函數(shù)僅簡單地把它們的參數(shù)復(fù)制到數(shù)據(jù)成員中:

            CDeviceList::CDeviceList(const GUID& guid)
                        {
                        m_guid = guid;
                        }
                        CDeviceListEntry::CDeviceListEntry(LPCTSTR linkname, LPCTSTR friendlyname)
                        {
                        m_linkname = linkname;
                        m_friendlyname = friendlyname;
                        }

            所有實際的工作都發(fā)生在CDeviceList::Initialize函數(shù)中。其執(zhí)行過程大致是這樣:先枚舉所有接口GUID與構(gòu)造函數(shù)得到的GUID相同的設(shè)備,然后確定一個“友好”名,我們希望向最終用戶顯示這個名字。最后返回找到的設(shè)備號。下面是這個函數(shù)的代碼:

            int CDeviceList::Initialize()
                        {
                        HDEVINFO info = SetupDiGetClassDevs(&m_guid, NULL, NULL, DIGCF_PRESENT | DIGCF_INTERFACEDEVICE);	<--1
                        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)	<--2
                        {
                        DWORD needed;
                        SetupDiGetDeviceInterfaceDetail(info, &ifdata, NULL, 0, &needed, NULL);				<--3
                        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];											<--4
                        if (!SetupDiGetDeviceRegistryProperty(info,
                        &did,
                        SPDRP_FRIENDLYNAME,
                        NULL,
                        (PBYTE) fname,
                        sizeof(fname),
                        NULL)
                        && !SetupDiGetDeviceRegistryProperty(info,
                        &did,
                        SPDRP_DEVICEDESC,
                        NULL,
                        (PBYTE) fname,
                        sizeof(fname),
                        NULL)
                        )
                        _tcsncpy(fname, detail->DevicePath, 256);
                        CDeviceListEntry e(detail->DevicePath, fname);							<--5
                        free((PVOID) detail);
                        m_list.Add(e);
                        }
                        SetupDiDestroyDeviceInfoList(info);
                        return m_list.GetSize();
                        }
            1. 該語句打開一個枚舉句柄,我們用它尋找寄存了指定GUID接口的所有設(shè)備。
            2. 循環(huán)調(diào)用SetupDiEnumDeviceInterfaces以尋找每個匹配的設(shè)備。
            3. 有兩項信息是我們需要的,接口的“細節(jié)”信息和設(shè)備實例信息。這個“細節(jié)”信息就是設(shè)備的符號名。因為它的長度可變,所以我們兩次調(diào)用了SetupDiGetDeviceInterfaceDetail。第一次調(diào)用確定了長度,第二次調(diào)用獲得了名字。
            4. 通過詢問注冊表中的FriendlyName鍵或DeviceDesc鍵,我們獲得了設(shè)備的“友好”名稱。
            5. 我們用設(shè)備符號名同時作為連接名和友好名創(chuàng)建了類CDeviceListEntry的一個臨時實例e

            其它全局性的設(shè)備初始化操作

            在AddDevice中還需要加入其它一些步驟來初始化設(shè)備對象,下面我將按順序描述這些步驟。

            初始化設(shè)備擴展

            設(shè)備擴展的內(nèi)容和管理全部由用戶決定。該結(jié)構(gòu)中的數(shù)據(jù)成員應(yīng)直接反映硬件的專有細節(jié)以及對設(shè)備的編程方式。大多數(shù)驅(qū)動程序都會在這里放入一些數(shù)據(jù)項,下面代碼聲明了一個設(shè)備擴展結(jié)構(gòu):

            typedef struct _DEVICE_EXTENSION {				<--1
                        PDEVICE_OBJECT DeviceObject;					<--2
                        PDEVICE_OBJECT LowerDeviceObject;				<--3
                        PDEVICE_OBJECT Pdo;						<--4
                        UNICODE_STRING ifname;					<--5
                        IO_REMOVE_LOCK RemoveLock;					<--6
                        DEVSTATE devstate;						<--7
                        DEVSTATE prevstate;
                        DEVICE_POWER_STATE devpower;
                        SYSTEM_POWER_STATE syspower;
                        DEVICE_CAPABILITIES devcaps;					<--8
                        ...
                        } DEVICE_EXTENSION, *PDEVICE_EXTENSION;
            1. 我模仿DDK中官方的結(jié)構(gòu)聲明模式聲明了這個結(jié)構(gòu)。
            2. 我們可以用設(shè)備對象中的DeviceExtension指針定位自己的設(shè)備擴展。同樣,我們有時也需要在給定設(shè)備擴展時能定位設(shè)備對象。因為某些函數(shù)的邏輯參數(shù)就是設(shè)備擴展本身(這里有設(shè)備每個實例的全部信息)。所以,我認為這里應(yīng)該有一個DeviceObject指針
            3. 我在一些地方曾提到過,在調(diào)用IoAttachDeviceToDeviceStack函數(shù)時,應(yīng)該把緊接著你下面的設(shè)備對象的地址保存起來。LowerDeviceObject成員用于保存這個地址。
            4. 有一些服務(wù)例程需要PDO的地址,而不是堆棧中某個高層設(shè)備對象的地址。由于定位PDO非常困難,所以最好的辦法是在AddDevice執(zhí)行時在設(shè)備擴展中保存一個PDO地址
            5. 無論你用什么方法(符號連接或設(shè)備接口)命名你的設(shè)備,都希望能容易地獲得這個名字。所以,這里我用一個Unicode串成員ifname來保存設(shè)備接口名。如果你使用一個符號連接名而不是設(shè)備接口,應(yīng)該使用一個有相關(guān)含義的成員名,例如“linkname”。
            6. 當(dāng)你調(diào)用IoDeleteDevice刪除這個設(shè)備對象時,需要使用一個自旋鎖解決同步安全問題,我將在第六章中討論同步問題。因此,需要在設(shè)備擴展中分配一個IO_REMOVE_LOCK對象。AddDevice有責(zé)任初始化這個對象。
            7. 你可能需要一個成員來記錄設(shè)備當(dāng)前的PnP狀態(tài)和電源狀態(tài)。DEVSTATE和POWERSTATE是枚舉類型變量,我假設(shè)事先已經(jīng)在頭文件中聲明了這些變量類型。我將在后面章節(jié)中討論這些狀態(tài)變量的用途。
            8. 電源管理的另一個部分涉及電源能力設(shè)置的恢復(fù),設(shè)備擴展中的devcaps結(jié)構(gòu)用于保存這些設(shè)置。

            下面是AddDevice中的初始化語句(著重設(shè)備擴展部分的初始化):

            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(...);
                        }

            初始化默認的DPC對象

            許多設(shè)備使用中斷來報告操作完成。我將在第七章“讀寫數(shù)據(jù)”中討論中斷處理,其中對中斷服務(wù)例程(ISR)能做什么做了嚴格的限定。特別是ISR不能調(diào)用用于報告IRP完成的例程(IoCompleteRequest)。利用DPC(推遲過程調(diào)用)可以繞過這個限制。你的設(shè)備對象中應(yīng)包含一個輔助DPC對象,它可以調(diào)度你的DPC例程,該對象應(yīng)該在設(shè)備對象創(chuàng)建后不久被初始化。

            NTSTATUS AddDevice(...)
                        {
                        IoCreateDevice(...);
                        IoInitializeDpcRequest(fdo, DpcForIsr);
                        }

            設(shè)置緩沖區(qū)對齊掩碼

            執(zhí)行DMA傳輸?shù)脑O(shè)備直接使用內(nèi)存中的數(shù)據(jù)緩沖區(qū)工作。HAL要求DMA傳輸中使用的緩沖區(qū)必須按某個特定界限對齊,而且設(shè)備也可能有更嚴格的對齊需求。設(shè)備對象中的AlignmentRequirement域表達了這個約束,它是一個位掩碼,等于要求的地址邊界減一。下面語句可以把任何地址圈入這個界限:

            PVOID address = ...;
                        SIZE_T ar = fdo->AlignmentRequirement;
                        address = (PVOID) ((SIZE_T) address & ~ar);

            還可以把任意地址圈入下一個對齊邊界:

            PVOID address = ...;
                        SIZE_T ar = fdo->AlignmentRequirement;
                        address = (PVOID) (((SIZE_T) address + ar) & ~ar);

            在這兩段代碼中,我使用了SIZE_T把指針類型(它可以是32位也可以是64位,這取決于編譯的目標平臺)轉(zhuǎn)化成一個整型,該整型與原指針有同樣的跨度范圍。

            IoCreateDevice把新設(shè)備對象中的AlignmentRequirement域設(shè)置成HAL要求的值。例如,Intel的x86芯片沒有對齊需求,所以AlignmentRequirement的默認值為0。如果設(shè)備需要更嚴格的緩沖區(qū)對齊(例如設(shè)備有總線主控的DMA能力,要求對齊數(shù)據(jù)緩沖區(qū)),應(yīng)該修改這個默認值,如下:

            if (MYDEVICE_ALIGNMENT - 1 > fdo->AlignmentRequirement)
                        fdo->AlignmentRequirement = MYDEVICE_ALIGNMENT - 1;

            我假設(shè)你在驅(qū)動程序某處已定義了一個名為MYDEVICE_ALIGNMENT的常量,它是2的冪,代表設(shè)備的數(shù)據(jù)緩沖區(qū)對齊需求。

            其它對象

            設(shè)備可能還有其它一些需要在AddDevice中初始化的對象。這些對象可能包括各種同步對象,各種隊列頭(queue anchors),聚集/分散列表緩沖區(qū),等等。事實上,在本書的其它地方討論這些對象的初始化更合適。

            初始化設(shè)備標志

            設(shè)備對象中有兩個標志位需要在AddDevice中初始化,并且它們在以后也不會改變,它們是DO_BUFFERED_IO和DO_DIRECT_IO標志。你只能設(shè)置并使用其中一個標志,它將決定你以何種方式處理來自用戶模式的內(nèi)存緩沖區(qū)。(我將在第七章中討論這兩種緩沖模式的不同,以及你如何選擇) 由于任何在后面裝入的上層過濾器驅(qū)動程序?qū)?fù)制你的標志設(shè)置,所以在AddDevice中做這個選擇十分重要。如果你在過濾器驅(qū)動程序裝入后改變了設(shè)置,它們可能會不知道。

            設(shè)備對象中有兩個標志位屬于電源管理范疇。與前兩個緩沖區(qū)標志不同,這兩個標志在任何時間都可以被改變。我將在第八章中詳細討論它們,但這里我先介紹一下。DO_POWER_PAGABLE意味著電源管理器將在PASSIVE_LEVEL級上向你發(fā)送IRP_MJ_POWER請求。DO_POWER_INRUSH意味著你的設(shè)備在上電時將汲取大量電流,因此,電源管理器將確保沒有其它INRUSH設(shè)備同時上電。

            設(shè)置初始電源狀態(tài)

            大部分設(shè)備一開始就進入全供電狀態(tài)。如果你知道你的設(shè)備的初始電源狀態(tài),應(yīng)該告訴電源管理器:

            POWER_STATE state;
                        state.DeviceState = PowerDeviceD0;
                        PoSetPowerState(fdo, DevicePowerState, state);

            電源管理的細節(jié)請見第八章。

            建立設(shè)備堆

            每個過濾器驅(qū)動程序和功能驅(qū)動程序都有責(zé)任把設(shè)備對象放到設(shè)備堆棧上,從PDO開始一直向上。你可以調(diào)用IoAttachDeviceToDeviceStack完成你那部分工作:

            NTSTATUS AddDevice(..., PDEVICE_OBJECT pdo)
                        {
                        PDEVICE_OBJECT fdo;
                        IoCreateDevice(..., &fdo);
                        pdx->LowerDeviceObject = IoAttachDeviceToDeviceStack(fdo, pdo);
                        }

            IoAttachDeviceToDeviceStack的第一個參數(shù)是新創(chuàng)建的設(shè)備對象的地址。第二個參數(shù)是PDO地址。AddDevice的第二個參數(shù)也是這個地址。返回值是緊接著你下面的任何設(shè)備對象的地址,它可以是PDO,也可以是其它低級過濾器設(shè)備對象。如果該函數(shù)失敗則返回一個NULL指針,因此你的AddDevice函數(shù)也是失敗的,應(yīng)返回STATUS_DEVICE_REMOVED

            清除DO_DEVICE_INITIALIZING標志

            在AddDevice中最后一件需要做的事是清除設(shè)備對象中的DO_DEVICE_INITIALIZING標志

            fdo->Flags &= ~DO_DEVICE_INITIALIZING;

            當(dāng)這個標志設(shè)置時,I/O管理器拒絕任何打開該設(shè)備句柄的請求或向該設(shè)備對象上附著其它設(shè)備對象的請求。在驅(qū)動程序完成初始化后,必須清除這個標志。在以前版本的Windows NT中,大部分驅(qū)動程序在DriverEntry中創(chuàng)建所有需要的設(shè)備對象。當(dāng)DriverEntry返回時,I/O管理器自動遍歷設(shè)備對象列表并清除該標志。但在WDM驅(qū)動程序中,設(shè)備對象在DriverEntry返回后才創(chuàng)建,所以I/O管理器不會自動清除這個標志,驅(qū)動程序必須自己清除它。

            posted on 2009-12-28 22:51 小默 閱讀(3285) 評論(1)  編輯 收藏 引用 所屬分類: Windows

            評論

            # re: [zz]AddDevice 2013-03-27 08:16 xCnWalker

            好文章,受用了。  回復(fù)  更多評論   

            導(dǎo)航

            統(tǒng)計

            留言簿(13)

            隨筆分類(287)

            隨筆檔案(289)

            漏洞

            搜索

            積分與排名

            最新評論

            閱讀排行榜

            国产精品久久自在自线观看| 国内精品欧美久久精品| 亚洲一区中文字幕久久| 精品久久久久久久久久中文字幕| 国内精品伊人久久久久av一坑| 久久国产高潮流白浆免费观看| 久久99国产精品久久99| 日本精品久久久久影院日本 | 久久天天躁狠狠躁夜夜躁2O2O| 久久久久久久久波多野高潮| 国产午夜福利精品久久2021 | 欧洲国产伦久久久久久久| 午夜精品久久久久久中宇| 国产激情久久久久影院| 2021少妇久久久久久久久久| 久久有码中文字幕| 久久精品国产久精国产| 亚洲精品tv久久久久久久久久| 99国产欧美久久久精品蜜芽| 手机看片久久高清国产日韩| 精品久久久久久无码人妻热| 久久婷婷久久一区二区三区| 久久久无码精品亚洲日韩蜜臀浪潮| 国内精品久久久久久中文字幕| 国产精品一区二区久久精品| aaa级精品久久久国产片| 久久久久久久久久久久中文字幕| 狼狼综合久久久久综合网| 国产亚洲色婷婷久久99精品| 久久国产精品77777| 久久一本综合| 99国产欧美精品久久久蜜芽| 国产精久久一区二区三区| 久久婷婷人人澡人人| 久久久亚洲欧洲日产国码二区| 99精品久久精品一区二区| 久久香蕉国产线看观看猫咪?v| 三级三级久久三级久久| 91精品国产91久久| 国产亚洲欧美精品久久久| 中文字幕日本人妻久久久免费 |