本文介紹了Windows 2000 WDM驅動程序結構及其編寫的注意事項,最後給出了一個簡單的WDM驅動程序及客戶端程序的源碼,希望能對大家有所幫助。
1. 簡介:
Windows 2000原名Windows NT 5.0是繼Windows NT 4.0的新一代操作系統,它不但繼承了Windows NT 4.0的種種優點,而且在技術上又有了許多的突破,其中一項就是對驅動程序結構的變化,即引入了全新的WDM (Win32 Driver Model)的驅動程序構架。說是新技術,其實早在1997年Microsoft就提出了該項技術並在Windows 98中得到了充分的應用,換句話說,Windows 98也支持WDM。這樣WDM就成為了一個跨平台的驅動程序模型,不僅如此WDM驅動程序還可以在不修改源代碼的情況下經過重新編譯後在非Intel平台上運行,可以不誇張的講WDM算得上是21世紀的驅動程序構架。
2.WDM的工作原理:
WDM是在NT 4.0驅動程序結構上發展起來的,所以它與NT 4.0的驅動程序極為相似,但是它卻有了本質上的提高,比如它支持USB、IEEE 1394、ACPI等全新的硬件標准。雖然Windows 98與Windows 2000都支持WDM,可是並不意味著Windows 98下的VxD可以在Windows 2000下運行,而NT下的VDD卻可以在Windows 98下運行。不過原先准備在兩個平台上同時運行需要編寫兩個截然不同的驅動程序,而現在只需要編寫一個WDM驅動程序就可以了。同NT 4.0驅動程序一樣,WDM驅動程序也是分層的,即不同層上的驅動程序有著不同的優先權,而Windows 9x下的VxD則沒有此結構。另外,WDM還引入了功能設備對象FDO(functional device object)與物理設備對象PDO(physical device object)兩個新概念來描述硬件,一個PDO代表一個真實硬件,在驅動程序看來則是一個FDO,見圖1。另外值得注意的是,一個硬件只允許有一個PDO,但卻可以擁有多個FDO,而在驅動程序中我們不是直接操作硬件而是操作相應的PDO與FDO。在Ring-3與Ring-0通訊方面,操作系統為每一個用戶請求打包成一個IRP(IO Request Packet)結構,將其發送至驅動程序並通過識別IRP中的PDO來識別是發送給哪一個設備的。另外,在驅動程序的加載方面WDM既不靠驅動程序名稱也不靠一個具有某種特殊意義的ID,而是依靠一個128位的GUID來識別驅動程序(Windows下許多東西都是靠此進行識別的)。
3.具體實現:
同許多應用程序一樣,WDM驅動程序是PE格式的,但是它卻沒有WinMain或main這樣的入口,取而代之的是DriverEntry:
NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject,
//不同於前面的PDO
IN PUNICODE_STRING RegistryPath)
{
DriverObject- >DriverExtension- >AddDevice =
AddDevice; // DriverExtension 中存放著驅動程序擴展信息,包括設備所需要的硬件資源等。
DriverObject- >MajorFunction[IRP_MJ_CREATE]
= RequestCreate;
DriverObject- >MajorFunction[IRP_MJ_CLOSE]
= RequestClose;
DriverObject- >MajorFunction[IRP_MJ_DEVICE_CONTROL]
= RequestControl;
DriverObject- >MajorFunction[IRP_MJ_PNP]
= RequestPnp;
return STATUS_SUCCESS;
}
在DriverEntry驅動程序要向操作系統登記並注冊一些消息處理器,而且還要指明是否對驅動程序輸入輸出的數據進行緩沖,另外還要我們提供一個AddDevice例程來把驅動程序添加到驅動程序堆棧中。其中,IRP_MJ_XXXXX為驅動程序所收到的系統消息,RequestXXXXX為相應的消息處理函數。在客戶端程序中,我們一般要采用DeviceIoControl通過自定義的控制碼與驅動程序通信(在VxD中大多也采用這種方式)。看看驅動程序所收到的系統消息,我們不難發現當用戶調用DeviceIoControl時操作系統就會向驅動程序發出一條IRP_MJ_DEVICE_CONTROL消息,以觸發RequestControl消息處理函數。
NTSTATUS RequestControl(IN PDEVICE_OBJECT
DeviceObject, IN PIRP Irp)
{
PIO_STACK_LOCATION IrpStack;
ULONG ControlCode;
ULONG InputLength,OutputLength;
NTSTATUS status;
IrpStack=IoGetCurrentIrpStackLocation(Irp);
//獲取當前IRP所在的I/O堆棧
ControlCode=IrpStack- >Parameters.DeviceIoControl.
IoControlCode; //取得控制碼
InputLength=IrpStack- >Parameters.DeviceIoControl.
InputBufferLength; //取輸入緩沖區大小
OutputLength=IrpStack- >Parameters.DeviceIoControl.
OutputBufferLength;//取輸出緩沖區大小
switch(ControlCode)
{
case HELLOWDM_IOCTL_HELLO: DbgPrint
("Hello from WDM.\n");//向調試器輸出字符串
status=STATUS_SUCCESS; //置返回值
break;
default: status=STATUS_INVALID_DEVICE_REQUEST;
//輸入的控制碼不支持
}
return CompleteRequest(Irp, status, 0);
//調用CompleteRequest通知操作系統完成IRP操作
}
在客戶端方面,先調用Setupapi.dll中的 SetupDiGetClassDevs並用上面提到的128位 GUID建立Ring-0與Ring-3接口:
HDEVINFO info=SetupDiGetClassDevs ((LPGUID)&GUID_HELLOWDM,NULL, //GUID_HELLOWDM 是128位GUID NULL,DIGCF_PRESENT|DIGCF_INTERFACEDEVICE); 然後使用SetupDiEnumDeviceInterfaces 對所獲得的接口進行枚舉以獲得接口數據,接著連續兩次調用SetupDiGetDeviceInterfaceDetail 獲得接口詳細信息,其中包括調用CreateFil e所需的一個型為\\.\0000000000000004# {3d93c5c0-0085-11d1-821e-0080c88327ab} 的字符串,最後調用方法和VxD的調用大體相同這裡就不贅述了。不過由於使用了 Setupapi.dll中的API所以還需要使用 SetupDiDestroyDeviceInfoList來釋放所申請的資源。
4.幾點說明:
由於WDM是跨平台和跨操作系統的的驅動程序模型,所以在編寫時一定不要使用匯編。另外,在編寫時還應注意對IRP_MJ_PNP消息的響應以及其他系統消息的傳遞,這裡的傳遞是向其它在驅動程序堆棧中的驅動程序而不是向客戶端程序,詳細的信息請參考本文所提供的例程。最後,由於筆者寫此文章時Windows 2000尚未正式發布,一切的編寫工作都是在Windows 98上用98DDK與VC6.0完成的,並且用Numega SoftIce 4.0調試通過。
本文配套源碼