图示 原图2-13 DRIVER_OBJECT数据结构
I/O管理器使用驱动程序对象来代表每个设备驱动程序,见图2-13。就象我们将要讨论的许多数据结构一样,驱动程序对象是部分不透明的。这意味着虽然 DDK头中公开了整个结构,但我们仅能直接访问或修改结构中的某些域。在图中,我把驱动程序对象的不透明域用灰背景表示。这些不透明域类似于C++类中的私有成员或保护成员,而透明域类似于公共成员。
1、DRIVER_OBJECT数据结构
DeviceObject(PDEVICE_OBJECT) 指向一个设备对象链表,每个设备对象代表一个设备。I/O管理器把多个设备对象连接起来并维护这个域。非WDM驱动程序的DriverUnload函数利用这个域来遍历设备对象列表,以便删除其中的设备对象。WDM驱动程序没有必要使用这个域。
DriverExtension(PDRIVER_EXTENSION) 指向一个不大的子结构,其中只有AddDevice(PDRIVER_ADD_DEVICE)成员可以直接访问,见图2-14。AddDevice是一个指针,它指向驱动程序中创建设备对象的函数。
图示 图2-14. DRIVER_EXTENSION数据结构
FastIoDispatch(PFAST_IO_DISPATCH)指向一个函数指针表,这些函数是由文件系统和网络驱动程序输出的。
DriverStartIo(PDRIVER_STARTIO)指向驱动程序中处理串行I/O请求的函数,I/O管理器自动为驱动程序串行化多个I/O请求。
DriverUnload (PDRIVER_UNLOAD)指向驱动程序中的清除函数。我将在DriverEntry函数后面讨论该函数,实际上,WDM驱动程序根本就没有什么重要的清除工作要做。
MajorFunction (array of PDRIVER_DISPATCH)是一个函数指针表,指向存在于驱动程序中的二十多种IRP处理函数。
2、DEVICE_OBJECT结构
WDM驱动程序可以调用IoCreateDevice函数创建设备对象,但设备对象的管理则由I/O管理器负责。
图示 图2-15 DEVICE_OBJECT结构
DriverObject(PDRIVER_OBJECT)指向与该设备对象相关的驱动程序对象,通常就是调用IoCreateDevice函数创建该设备对象的驱动程序对象。过滤器驱动程序有时需要用这个指针来寻找被过滤设备的驱动程序对象,然后查看其MajorFunction表项。
NextDevice(PDEVICE_OBJECT)指向属于同一个驱动程序的下一个设备对象。是这个域把多个设备对象连接起来,起始点就是驱动程序对象中的DeviceObject成员。WDM驱动程序没有必要使用这个域。
CurrentIrp(PIRP)指向最近发往驱动程序StartIo函数的I/O请求包。
Characteristics(ULONG)是另一组标志位,描述设备的可选特。
DeviceExtension(PVOID)指向一个由用户定义的数据结构,该结构可用于保存每个设备实例的信息。I/O管理器为该结构分配空间,但该结构的名字和内容完全由用户决定。一个常见的做法是把该结构命名为DEVICE_EXTENSION。使用给定的设备对象指针fdo可访问这个用户结构,代码如下:
PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION) fdo->DeviceExtension;
StackSize(CCHAR)统计从该设备对象开始向下直到PDO之间的设备对象个数。该域的目的是告诉其它代码,如果把该设备对象的驱动程序作为其 IRP的第一发送对象,那么应在这个IRP中创建多少个堆栈单元(stack location)。WDM驱动程序通常不需要修改该值,因为创建设备堆栈的支持函数会自动完成这个任务。
在前面DEVICE_OBJECT的讨论中,通过个NextDevice域把所有属于特定驱动程序的设备对象水平地连接在一起,但还没有描述把多个设备对象垂直地连接成一个设备堆栈的方法,即从上层FiDO到FDO,再到下层FiDO,最后到PDO。不透明域AttachedDevice就是用于这个目的。从PDO开始,每个设备对象都有指向上一层设备对象的指针。由于没有已公开的向下方向的指针,所以驱动程序必须自己记住下层设备对象是谁。(实际上,IoAttachDeviceToDeviceStack确实在一个结构中建立了向下方向的指针,但DDK中没有完全声明该结构。不要试图去查出这个结构,它随时都有可能被修改)
AttachedDevice 域故意没有公开,因为要正确地使用该域需要与删除设备对象的代码同步。但我们可以调用IoGetAttachedDeviceReference函数,该函数在给定的堆栈中寻找最上层设备对象并增加参考计数,这样可以防止该设备对象被过早地从内存中删除。如果你要自己下到PDO,可以向设备发送一个 IRP_MJ_PNP请求,其副功能码为IRP_MN_QUERY_DEVICE_RELATIONS,Type参数为 TargetDeviceRelation。PDO驱动程序将返回PDO的地址。但是,我猜想这个IRP是保留给系统使用的,所以你最好不要使用这个 IRP。我们完全可以在第一次建立设备对象时记住PDO的地址。
3、DriverEntry
DriverEntry是内核模式驱动程序主入口点常用的名字。
extern "C" NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath)
{
...
}
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的第二个参数是设备服务键的键名。这个串不是长期存在的(函数返回后可能消失),如果以后想使用该串就必须先把它复制到安全的地方。
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);
对于WDM驱动程序的DriverEntry例程,其主要工作是把各种函数指针填入驱动程序对象。这些指针为操作系统指明了驱动程序容器中各种子例程的位置。DriverExtension->AddDevice 指向驱动程序的AddDevice函数。PnP管理器将为每个硬件实例调用一次AddDevice例程。
DriverStartIo 如果驱动程序使用标准的IRP排队方式,应该设置该成员,使其指向驱动程序的StartIo例程。
MajorFunction 是一个指针数组。每个WDM驱动程序必须能处理PNP、POWER、SYSTEM_CONTROL这三种请求。