当 I/O 管理器加载一个设备驱动程序时,它会创建一个驱动程序对象,该对象在对象管理器目录中的路径为:\Driver\
与驱动程序相关的另一种对象是设备对象。每个设备对象代表了系统中的一个设备,包括逻辑设备和物理设备。正常情况下,有两种途径可创建设备对象:即插即用管理器在检测到设备时,通过调用驱动程序的AddDevice 例程来创建设备对象;或者,非即插即用驱动程序在它们的初始化例程中创建设备对象。每个设备对象都必定有一个为它负责的驱动程序。对设备对象的各种操作实际上是由为它负责的驱动程序中的例程来完成的。一个驱动程序可以支持多个设备,所以,驱动程序对象中有一个链表记录了它所负责的所有设备对象。
在Windows中,设备对象的创建是通过 I/O 管理器的IoCreateDevice函数来完成的,函数原型如下:
DriverObject 参数指向负责该设备的驱动对象;DeviceExtensionSize 参数定义了待创建设备对象的扩展部分大小,此扩展部分是由驱动程序来指定和使用的;DeviceName 可选参数指定了设备的名称。DeviceType 参数定义了设备的类型,DEVICE_TYPE 是无符号整数类型, Microsoft 已经预定义了所有常用的设备,它们的整数值小于 32767, 若创建者需要使用自定义的设备类型,可以使用大于 32767 的值。关于这些预定义值,参见 public\sdk\inc\devioctl.h 文件中的常量定义。DeviceCharacteristics参数指定了设备的特征; Exclusive 参数指定了在创建设备对象时是否使用互斥标志;DeviceObject 参数是一个输出参数,用于存放所创建的设备对象。
IoCreateDevice函数,首先根据参数中指定的要求,构造出设备的名称,并且创建一个安全描述符,用于对该设备的访问控制。然后,调用ObCreateObject 函数创建一个 IoDeviceObjectType 类型的内核对象。如果由于自动产生的名称发生冲突而导致ObCreateObject 调用不成功,则重试此过程。然后,IoCreateDevice 函数初始化所建的设备对象中的成员,并调用ObInsertObject 函数,将设备对象插入到进程的句柄表中。最后,设定该设备对象中的驱动程序对象,并将设备对象插入到驱动程序对象的设备链表中,因而将设备对象与驱动程序对象关联起来。
下面看一下驱动程序对象和设备对象的定义。以下是驱动程序对象的数据结构:
typedef struct _DRIVER_OBJECT{
CSHORT Type;
CSHORT Size;
PDEVICE_OBJECT DeviceObject; //指向设备对象,所有的设备对象构成一个链表
ULONG Flags; //驱动程序标志
PVOID DriverStart; //驱动程序映像起始地址
ULONG DriverSize; //驱动程序映像大小
PVOID DriverSection; //指向驱动程序映像的内存区对象
PDRIVER_EXTENSION DriverExtension; //指向驱动程序对象的扩展部分
UNICODE_STRING DriverName; //驱动程序名称
PUNICODE_STRING HardwareDatabase; //只想注册表中包含硬件信息的路径
PFAST_IO_DISPATCH FastIoDispatch; //指向快速 I/O 的分发结构
PDRIVER_INITIALIZE DriverInit; // 驱动程序的初始化例程
PDRIVER_STARTIO DriverStartIo; // 驱动程序的启动I/O例程
PDRIVER_UNLOAD DriverUnload; //驱动程序的卸载例程
PDRIVER_DISPATCH MajorFunction[IRP_MJ_MAXIMUM_FUNCTION + 1];
} DRIVER_OBJECT;
//以下是扩展部分的数据结构 DRIVER_EXTENSION的定义
typedef struct _DRIVER_EXTENSION {
struct _DRIVER_OBJECT *DriverObject; //指向驱动程序
// AddDevice 是一个函数指针, 当即插即用管理器检测到一个新的设备由该驱动程序负责
// 时,调用该函数 ,以便通知该驱动程序
PDRIVER_ADD_DEVICE AddDevice;
ULONG Count; // 记录了重新初始化调用的次数
UNICODE_STRING ServiceKeyName; // 驱动程序的服务名称
PIO_CLIENT_EXTENSION clientDriverExtension; // 指向驱动程序的客户扩展部分
PFS_FILTER_CALLBACKS FsFilterCallbacks; //用于文件系统过滤驱动程序
}DRIVER_EXTENSION, *PDRIVER_EXTENSION;
驱动程序对象中的 MajorFunction 数组包含了一组例程,当 I/O 管理器接收到一个 I/O 请求时,它将根据 I/O 请求中的有关信息,找到设备对象的驱动程序对象,并调用驱动程序中相应的例程来处理该 I/O 请求。通常, 设备驱动程序的初始化例程会填充MajorFunction 数组中的例程。对于初始化例程未填充的数组项,创建驱动程序对象的函数(如IopLoadDriver 和 IoCreateDriver)会将其填充为 IopInvalidDeviceRequest 函数。
下面是设备对象的数据结构:
typedef struct _DEVICE_OBJECT{
CSHORT Type;
USHORT Size;
LONG ReferenceCount; //引用计数值
struct _DRIVER_OBJECT *DriverObject; // 指向关联的驱动程序对象
struct _DEVICE_OBJECT *NextDevice; // 指向关联同一驱动程序对象的下一个设备
//对象
struct _DEVICE_OBJECT *AttachedDevice; // 附载的设备,与AttachedTo 域构成双链
//表关系
struct _IRP *CurrentIrp; // 当前正在处理的 I/O 请求包
PIO_TIMER Timer; // 设备对象的定时器
ULONG Flags; // 设备对象标志,以 DO_作为前缀的一组常量
ULONG Characteristics; // 设备的特征,以FILE_作为前缀的一组常量
PVPB Vpb; // 指向设备的卷参数块
PVOID DeviceExtension; // 指向设备对象的扩展部分
DEVICE_TYPE DeviceType; // 设备类型
CCHAR StackSize; //设备栈的大小
union {
LIST_ENTRY ListEntry; //用户文件系统的设备对象,形成一个链表
WAIT_CONTEXT_BLOCK wcb; //等待环境块,用于与控制器对象协作
} Queue;
ULONG AlignmentRequirement; //缓冲区的对齐要求, 其值等于对其边界减一
KDEVICE_QUEUE DeviceQueue; //设备队列,存放针对该设备的 I/O 请求
KDPC Dpc;
ULONG ActiveThreadCount; // 用户文件系统:使用此设备对象的线程数
PSECURITY_DESCRIPTOR SecurityDescriptor; //设备的安全描述符
KEVENT DeviceLock; //设备锁
USHORT SectorSize; //扇区大小
USHORT Sparel;
struct _DEVOBJ_EXTENSION *DeviceObjectExtension; // 指向扩展部分
USHORT Reserved;
} DEVICE_OBJECT;
typedef struct _DEVICE_OBJECT *PDEVICE_OBJECT;
设备对象扩展部分的定义如下:
typedef struct _DEVOBJ_EXTENSION {
CSHORT Type;
USHORT Size;
PDEVICE_OBJECT DeviceObject; //指向关联的设备对象
ULONG PowerFlags; // 电源标志
struct _DEVICE_OBJECT_POWER_EXTENSION *Dope; //设备对象的电源扩展部分
ULONG ExtensionFlags; //设备对象扩展标志
PVOID DeviceNode; //设备节点域,由即插即用管理器使用
PDEVICE_OBJECT AttachedTo; //当前设备对象被附载到此设备对象
//以下三个域用户 IoStart*函数
LONG StartIoCount; // 已启动但未完成的 I/O的数量
LONG StartIoKey; // 下一个启动 I/O 的键
ULONG StartIoFlags; // 启动 I/O 标志
PVPB Vpb; // 已挂载卷的VPB, 用于文件系统的卷设备对象
} DEVICE_EXTENSION, *PDEVOBJ_EXTENSION;
设备对象描述了一个特定设备的状态信息,包括它所接收到的 I/O 请求和设备的电源特性等。正如我们在 IoCreateDevice 函数中看到的那样, 设备对象的 DriverObject 域指向负责该设备的驱动程序,而它的 NextDevice 域构成了同属一个驱动程序的设备对象单链表。IoCreateDevice 调用 IopInsertRemoveDevice 函数,将设备对象插入到此链表中。另一方面,设备对象的 AttachedDevice 域和扩展部分的 AttachedTo 域构成了一个双链表节点中的前后指针。在Windows 的层次驱动程序模型中,I/O请求可以被传递给一个设备栈进行处理。设备栈中的设备对象相互链接起来,创建这些设备对象的驱动程序相互协作来处理针对特定设备的 I/O 请求。因而, 当 I/O 管理器接收到针对这一设备的 I/O 请求时, 它会一次将该 I/O 请求传递给这些设备对象,交给它们处理。设备对象的StackSize 域制定了为处理 I/O 请求而最小需要的栈深度,而 AttachedTo 成员将设备展中的设备对象子栈顶向底层(接近硬件设备的设备对象)链接起来,AttachedDevice 成员则相反地自底向上将这些设备对象链接起来。
图6.5显示了在虚拟机环境中 Windows Server 2003 SP1系统的三个设备对象所构成的设备栈,我们可以看到设备对象中的 AttachedDevice 和 AttachedTo 成员所形成的链表结构。驱动程序 ACPI 创建了最底层的设备对象 “\Device\0000003b” ,它要求最小的栈深度为4,这是在 ACPI驱动程序中指定的。驱动程序 i8042prt 创建了中间的无名设备对象,它的 AddDevice 例程调用 IoAttachDeviceToDeviceStack 函数,完成此无名设备对象的挂载工作。进一步,驱动程序kbdclass创建了最上面的设备对象 "\Device\KeyboardClass0" , 类似地,它的 AddDevice 例程调用 IoAttachDevice 函数,完成 KeyboardClass0 设备对象的挂载工作。