DriverEntry程序

DriverEntry程序

在以前的部分中,我講了即插即用管理器裝載硬件所需要的驅動並調用其AddDevice函數。一個特定的驅動程序可能會被一個以上的相似硬件所使用,並且存在一些只需要在其第一次加載時被執行一次的全局初始化操作。DriverEntry程序負責這些全局初始化操。

extern "C" NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject,

   IN PUNICODE_STRING RegistryPath)

{

……

……

……

   }

 

注:

如果你用的是標準的編譯程序,你能以“DriverEntry”程序爲主入口點來調用內核模式驅動程序,這是因爲在構建腳本(Build Script)中已經定義,其會命令連接器將其連接爲默認的主入口點,你最好也令你的代碼和這相符(否則修改構建腳本,但這有必要麼?)

 

例子代碼:

你可以用本章所討論的想法用STUPID例子驅動試驗。STUPID僅僅只實現了DriverEntry和AddDevice。其很想我初學的時候寫的第一個驅動程序。

 

    在我描述你要往DriverEntry中寫的代碼之前,我打算說一下關於其自身的函數原型。你和我並不知道(除非我們仔細看了構建腳本中的編譯器選項),在x86的計算機的編譯器上,內核模式函數和你驅動程序中使用的函數都使用__stdcall約定。這雖然對你的編程沒什麼影響,但當你調試的時候你應該記住這一點。我有時使用extern “C”編譯指令這是因爲我通常在C++編譯單元中打包我的代碼——主要是這樣我可以隨時聲明變量來而不用像C語言中只能在左大括號的後面聲明。該預編譯指令將禁止編譯器生成C++形式的外部函數名修飾,這樣連接器就能找到該函數。使用這個指令編譯後,驅動程序入口函數的外部名將爲_DriverEntry@8

關於DriverEntry原型的另一點是那些“IN”關鍵字。INOUT都是在DDK中被定義爲空串的兩個噪聲詞。其最初的目的是令其爲註釋功能。也就是說當你看到IN參數,你應該認爲該參數是純粹用於輸入目的的,OUT參數則是用於輸出目的,而IN OUT參數既用於輸入也用於輸出。其實,DDK頭文件並不總是直觀的使用這些關鍵字,也並不怎麼使用它。一個例子:DriverEntry聲明瞭DriverObject指針是IN型;當然,雖然你沒改變指針,但你確實改變其指向的對象。

關於需要你注意的該函數原型的最後一點是其聲明瞭一NTSTATUS值作爲返回值。NTSTATUS實際上只是一個長整型。但爲了你代碼更好的可讀性你應該用類型定義名NTSTATUS來代替LONG。很多內核模式支持程序都能返回NTSTATUS狀態碼,狀態碼的定義你可以在DDK的頭文件NTSTATUS.H中找到。關於狀態碼在下一章我將會介紹更多。目前,你只需知道當你的DriverEntry函數執行完畢時其會返回狀態碼。


DriverEntry概述

DriverEntry的第一個參數是一個指針,指向一個剛被初始化的驅動程序對象,該對象就代表你的驅動程序。WDM驅動程序的DriverEntry程序會完成對這個對象的初始化並返回。而非WDM驅動程序則有大量額外的工作要做——它們必須探測自己的硬件,爲硬件創建設備對象(用於代表硬件),配置並初始化硬件使其正常工作。而對於WDM驅動程序,頗麻煩的硬件探測和配置工作由PnP管理器自動完成,我將在第六章討論PnP。如果你想知道非WDM驅動程序是如何初始化自身的,參見Art Baker的《The Windows NT Device Driver Book (Prentice Hall, 1997)》、Viscarola和Mason的《Windows NT Device Driver Development (Macmillan, 1998)》。

    DriverEntry的第二個參數是註冊表中設備服務鍵的鍵名。這個串不是長期存在的(函數返回後可能消失),如果你打算以後再使用該串則必須先把它複製到安全的地方。在WDM中我曾經構造該串的唯一用途是將其作爲WMI註冊的一部分(請參閱第10章)

WDM驅動程序的DriverEntry的主要工作是給驅動對象填入各種的函數指針。這些指針爲操作系統指明瞭驅動程序容器中各種子程序的位置。驅動對象中的指針包括如下內容:

n         DriverUnload

    用來來指向你創建的清除程序。I/O管理器只會在卸載驅動之前調用該程序。如果沒有任何需要清除的,你需要給系統一個DriverUnload函數來動態地卸載你的驅動。

n         DriverExtension->AddDevice

    用來指向AddDevice函數。即插即用管理器會爲每一個你負責的硬件實例調用一次AddDevice。由於AddDevice對於WDM驅動的工作方式非常重要,在下一節(AddDevice程序)中我將具體闡述它是如何工作的。

n         DriverStartIo

如果驅動程序使用標準的隊列I/O請求方式,你應該設置這組驅動對象指向StartIo程序。不要擔心(但確實是這樣)是否明白我所說的“標準的”隊列方式。在第五章你就全明白了,而且你會發現WDM驅動不應該使用它。

n         MajorFunction

    I/O管理器初始化該函數指針向量來指向啞元派遣函數,該啞元派遣函數返回每個請求的失敗。你大概只需要處理幾種IRP的類型——否則驅動程序基本上就廢了,所以至少應該設置與那幾種IRP類型相對應的指針元素,使它們指向相應的派遣函數。第五章詳細地討論了IRP和派遣函數。現在你只需知道你必須要處理兩種IRP而且可能還會有一些其他的IRP需要處理。

    如下是一個比較完整的DriverEntry程序:

 

extern "C" NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject,
    IN PUNICODE_STRING RegistryPath)
    {
       

1


    DriverObject->DriverUnload = DriverUnload;
    DriverObject->DriverExtension->AddDevice = AddDevice;
    

2


    DriverObject->MajorFunction[IRP_MJ_PNP] = DispatchPnp;
    DriverObject->MajorFunction[IRP_MJ_POWER] = DispatchPower;
    DriverObject->MajorFunction[IRP_MJ_SYSTEM_CONTROL] = 
      DispatchWmi;
    

3


……
……
……
    

4


    servkey.Buffer = (PWSTR) ExAllocatePool(PagedPool,
      RegistryPath->Length + sizeof(WCHAR));
    if (!servkey.Buffer)
      return STATUS_INSUFFICIENT_RESOURCES;
    servkey.MaximumLength = RegistryPath->Length + sizeof(WCHAR);
    RtlCopyUnicodeString(&servkey, RegistryPath);
    servkey.Buffer[RegistryPath->Length/sizeof(WCHAR)] = 0;
    

5


    return STATUS_SUCCESS;
    }

 

 

1.      這兩條語句設定了在驅動中別處的函數指針的入口點。我選擇了能代表它們的函數:DriverUnload和AddDevice

2.      每個WDM驅動必須處理PNP,POWER,和SYSTEM_CONTROL I/O請求。在這裏你要給這些請求派遣函數。現在的這個IRP_MJ_SYSTEM_CONTROL在早期的Windows XP DDK的測試版裏被叫做IRP_MJ_SYSTEM_WMI,所以我在這裏把這個派遣函數叫做DispatchWmi。

3.      在省略號的位置,你可以添加一些附加的MajorFunction指針。

4.      如果你打算在你驅動的別的地方要訪問註冊表服務鍵值,這裏是一個不錯的複製RegistryPath字符串的地方。我已經假設你在別處已經聲明瞭一個名叫servkey的UNICODE_STRING的全局變量。我將在下一章中描述Unicode字符串的工作方式。

5.      若執行成功則返回STATUS_SUCCESS。若發生了錯誤,其會返回一個錯誤代碼,該代碼將來自NTSTATUS.H中已經被定義到的標準錯誤代碼,也可能來自由你自己定義的代碼。STATUS_SUCCESS的值爲0。

 

 

子程序命名

許多驅動程序作者其驅動中的子程序命名都包含了驅動程序的名字。例如,許多程序員會將AddDevice和DriverUnload函數命名爲如Stupid_AddDevice,Stupid_DriverUnload這樣的形式。Microsoft早期版本的WindDbg調試器強制要求程序員使用像這樣的約定因爲其已經有了一個全局命名空間了。該調試器之後的版本沒有這個限制,但你仍可以在DDK的例子中看到這一約定。

現在我。是一個狂熱的代碼重用者和一個冷漠的打字員。就我來說,在每個項目中給同樣的子程序用一個名字是很舒服的事。這樣我只需要將一代碼中的子程序直接粘貼到另一段代碼上而不再需要做一系列名字上的修正了。我還可以清晰地以此來比較兩個驅動的不同。

 

DriverUnload

WDM驅動的DriverUnload函數的目的是清理DriverEntry所做的所有全局初始化操作。其幾乎無事可做。若你在DriverEntry中複製了RegistryPath字符串,DriverUnload則會在此釋放內存。代碼如下:

 

VOID DriverUnload(PDRIVER_OBJECT DriverObject)

   {

   RtlFreeUnicodeString(&servkey);

   }

 

 

若DriverEntry返回失敗,系統不會調用DriverUnload程序。因此,DriverEntry若在返回一錯誤之前產生了一些需要清理的結果,其才得執行該程序。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章