大多数中间层和最低层驱动程序仅仅使用设备或者控制器扩展来维护必要的设备状态,并 为其他驱动程序确定的数据,诸如系统定义对象、自旋锁、互锁队列,以及其他驱动程序 定义的数据提供存储空间。然而,如果没有这样的缓冲,驱动程序的设备不能充分地被服 务,驱动程序能在设备开始时使用内存管理器的支持例程分配邻接的或者非高速缓存的内 部缓冲区。更多信息参见第16章中“管理内存使用”。 32设备对象和设备扩展 设备对象,像驱动程序对象一样,对驱动程序是部分地不透明的。因为它们的驱动程序必 须通过由 loCreateDevice返回的 DeviceObject指针访问这些领域,驱动程序作者必须知 道某个域名称和与设备对象有关的系统定义的符号常量,因为它们的驱动程序必须通过 Deviceob ject指针访问这些域,这个 DeviceOb ject指针是由 IoCreateDevice返回的,并 且被传递给大多数标准的驱动程序例程。 注意,任何通过一种返回的 DeviceOb ject指针而可以访问的域的位置能从一种 Windows平 台或版本改变成为另一个。此外,在任何对象之内的“未发表过的”域实际上是难以访问 的:即,驱动程序不应该访问它们。已经依赖于系统定义对象中任何域的位置,或访问对 象中未公开的域的驱动程序,会削减它自己未来的可移植性和与其他驱动程序的互操作 图3.2说明一个设备对象,它在系统之内代表一种物理、逻辑、或者虚拟设备。驱动程序 通过从它的 AddDevice例程调用 IoCreateDevice来创建一个或多个设备对象 图3.2设备对象 图3.2显示了对最低层和中间层驱动程序的作者特别重要的域名称和常量。以下小节中将 被更详细描述这些域。 3.2.1定义设备扩展 对于大多数中间层和最低层驱动程序,设备扩展是与设备对象有关的最重要的数据结构。 其内部的结构是驱动程序定义的,并且它被典型地用于 保持设备状态信息 为任何内核定义的对象或者被驱动程序使用的其他系统资源提供存储,如自旋锁。 保持驱动程序必须拥有的任何数据是常驻的,并且在系统空间内,以便完成它的I/O 操作。 I/0管理器从常驻的系统空间内存中分配设备对象内存和扩展:即, IoCreateDevice从非 页式存储池中为设备对象分配内存 所有被赋予一个IRP的标准驱动程序例程也被赋予一个指向代表请求I/0操作的目标设备 的设备对象的指针,如在第2章中所述的那样。因而,任何给定IRP的驱动程序例程能通 过这个指向合适设备对象的指针访问相应的 Deviceextension。通常,对最低层驱动程序 的ISR来说, DeviceOb ject指针也是一个输入参数 图3.3针对最低层驱动程序设备对象的扩展显示了驱动程序定义的数据的一个代表集。较 高层的驱动程序将不再为中断对象指针提供存储,这里的指针由 IoConnectInterrupt返 回,并到 KcSynchronizeExecution和 loDisconnectInterrupt。然而,如果驱动程序有
31 大多数中间层和最低层驱动程序仅仅使用设备或者控制器扩展来维护必要的设备状态,并 为其他驱动程序确定的数据,诸如系统定义对象、自旋锁、互锁队列,以及其他驱动程序 定义的数据提供存储空间。然而,如果没有这样的缓冲,驱动程序的设备不能充分地被服 务,驱动程序能在设备开始时使用内存管理器的支持例程分配邻接的或者非高速缓存的内 部缓冲区。更多信息参见第 16 章中“管理内存使用”。 3.2 设备对象和设备扩展 设备对象,像驱动程序对象一样,对驱动程序是部分地不透明的。因为它们的驱动程序必 须通过由 IoCreateDevice 返回的 DeviceObject 指针访问这些领域,驱动程序作者必须知 道某个域名称和与设备对象有关的系统定义的符号常量,因为它们的驱动程序必须通过 DeviceObject 指针访问这些域,这个 DeviceObject 指针是由 IoCreateDevice 返回的,并 且被传递给大多数标准的驱动程序例程。 注意,任何通过一种返回的 DeviceObject 指针而可以访问的域的位置能从一种 Windows 平 台或版本改变成为另一个。此外,在任何对象之内的“未发表过的”域实际上是难以访问 的:即,驱动程序不应该访问它们。已经依赖于系统定义对象中任何域的位置,或访问对 象中未公开的域的驱动程序,会削减它自己未来的可移植性和与其他驱动程序的互操作 性。 图 3.2 说明一个设备对象,它在系统之内代表一种物理、逻辑、或者虚拟设备。驱动程序 通过从它的 AddDevice 例程调用 IoCreateDevice 来创建一个或多个设备对象。 图 3.2 设备对象 图 3.2 显示了对最低层和中间层驱动程序的作者特别重要的域名称和常量。以下小节中将 被更详细描述这些域。 3.2.1 定义设备扩展 对于大多数中间层和最低层驱动程序,设备扩展是与设备对象有关的最重要的数据结构。 其内部的结构是驱动程序定义的,并且它被典型地用于: ▪ 保持设备状态信息。 ▪ 为任何内核定义的对象或者被驱动程序使用的其他系统资源提供存储,如自旋锁。 ▪ 保持驱动程序必须拥有的任何数据是常驻的,并且在系统空间内,以便完成它的 I/O 操作。 I/O 管理器从常驻的系统空间内存中分配设备对象内存和扩展:即,IoCreateDevice 从非 页式存储池中为设备对象分配内存。 所有被赋予一个 IRP 的标准驱动程序例程也被赋予一个指向代表请求 I/O 操作的目标设备 的设备对象的指针,如在第 2 章中所述的那样。因而,任何给定 IRP 的驱动程序例程能通 过这个指向合适设备对象的指针访问相应的 DeviceExtension。通常,对最低层驱动程序 的 ISR 来说,DeviceObject 指针也是一个输入参数。 图 3.3 针对最低层驱动程序设备对象的扩展显示了驱动程序定义的数据的一个代表集。较 高层的驱动程序将不再为中断对象指针提供存储,这里的指针由 IoConnectInterrupt 返 回,并到 KcSynchronizeExecution 和 IoDisconnectInterrupt。然而,如果驱动程序有
CustomTimerDpc例程,较高层驱动程序将为图3.3中显示的定时器和DPC对象提供存储。 较高层驱动程序也可以为执行的自旋锁和互锁的工作队列提供存储 除为中断对象指针提供存储之外,如果最低层设备驱动程序为两个或更多的设备以不同的 向量处理中断,或者它拥有多个ISR,那么它必须为中断自旋锁提供存储。更多的关于注 册ISR的信息,参见本章中随后的“中断对象”。 图3.3最低层驱动程序的样例设备扩展 如图3.3所示,大多数驱动程序发现在它们的设备扩展中存储指向它们设备对象的指针是 方便的。驱动程序也可以在扩展中为设备保持一份资源表的拷贝。 较高层驱动程序典型地将指向相邻的较低层驱动程序的设备对象指针存储到其设备扩展 中。在较高层驱动程序已经在IRP中创建相邻的较低层驱动程序的I/0栈位置之后,它必 须把指向较低层驱动程序的设备对象的指针传递到 loCalldriver,如第2章中所述。 也要注意到,任何为低层驱动程序分配IRP的较高层驱动程序必须指定新的IRP应该有多 少栈位置。尤其是,如果较高层驱动程序调用 loMakeAssociatedIrp(仅 Windows2000 有)、 oAllocateIrp、或者 loInitializelrp,为了给这些支持例程提供正确的 Stacksize作为参数,它必须访问相邻的较低层驱动程序的目标设备对象以读取其栈大小 的值 当较高层驱动程序能通过由 loAttachDevicetoDeviceStack返回的指针从相邻的较低层驱 动程序的设备对象中读取数据时,这样的驱动程序必须遵循这些实现方针: 从不试图把数据写到较低层驱动程序的设备对象中 前面方针的唯一例外情况是文件系统,它在较低层可拆除媒介驱动程序的设备对象的 Flags中设置和清除 DO VERIFY VOLUM 从不试图因下列原因访问较低层驱动程序的设备扩展 在两个驱动程序之间,没有同步访问单个设备扩展的安全方法。 实现这样一种后门通信方案的一对驱动程序不能单个地被升级,不改变现存的驱 动程序资源不能在它们之间插入中间驱动程序,并且从一个 Windows平台到下 个不能被容易地重编辑和移植。 为了保有从一种 Windows平台或者版本到下一个版本与低层驱动程序的互操作性能,较高 层驱动程序必须重用给它们的IRP,或者必须创建新的IRP,并且它们必须使用 loCalldriver把请求传送到较低层驱动程序 3.2.2创建设备对象和设备扩展 当每个驱动程序调用 LoCreateDevicc创建设备对象时,驱动程序在 Deviceextensionsize 参数中指定了设备扩展的大小 大多数驱动程序仅仅从 AddDevice例程调用 IoCreateDevice。对于一些驱动程序,例如必 须对驱动器布局I0CTL作出响应的磁盘驱动程序,这个调用也能由 Dispatch例程来作 除了某些为驱动程序(它与创建设备对象的类驱动程序成对)之外,所有的驱动程序必须 从其 AddDevice例程调用 loCreateDevicc一次或多次,以创建设备对象,这个设备对象代 表它为其处理I/0请求的每一种物理、逻辑、或者虚拟设备。否则,对于驱动程序没有为 其创建设备对象的任何目标设备,驱动程序将不能为这些设备获取IRP。 除设备扩展的大小之外, IoCreateDevice要求确定以下参数:
32 CustomTimerDpc 例程,较高层驱动程序将为图 3.3 中显示的定时器和 DPC 对象提供存储。 较高层驱动程序也可以为执行的自旋锁和互锁的工作队列提供存储。 除为中断对象指针提供存储之外,如果最低层设备驱动程序为两个或更多的设备以不同的 向量处理中断,或者它拥有多个 ISR,那么它必须为中断自旋锁提供存储。更多的关于注 册 ISR 的信息,参见本章中随后的“中断对象”。 图 3.3 最低层驱动程序的样例设备扩展 如图 3.3 所示,大多数驱动程序发现在它们的设备扩展中存储指向它们设备对象的指针是 方便的。驱动程序也可以在扩展中为设备保持一份资源表的拷贝。 较高层驱动程序典型地将指向相邻的较低层驱动程序的设备对象指针存储到其设备扩展 中。在较高层驱动程序已经在 IRP 中创建相邻的较低层驱动程序的 I/O 栈位置之后,它必 须把指向较低层驱动程序的设备对象的指针传递到 IoCallDriver,如第 2 章中所述。 也要注意到,任何为低层驱动程序分配 IRP 的较高层驱动程序必须指定新的 IRP 应该有多 少栈位置。尤其是,如果较高层驱动程序调用 IoMakeAssociatedIrp(仅 Windows 2000 有)、IoAllocateIrp、或者 IoInitializeIrp,为了给这些支持例程提供正确的 StackSize 作为参数,它必须访问相邻的较低层驱动程序的目标设备对象以读取其栈大小 的值。 当较高层驱动程序能通过由 IoAttachDeviceToDeviceStack 返回的指针从相邻的较低层驱 动程序的设备对象中读取数据时,这样的驱动程序必须遵循这些实现方针: ▪ 从不试图把数据写到较低层驱动程序的设备对象中。 前面方针的唯一例外情况是文件系统,它在较低层可拆除媒介驱动程序的设备对象的 Flags 中设置和清除 DO_VERlFY_VOLUME。 ▪ 从不试图因下列原因访问较低层驱动程序的设备扩展: ▪ 在两个驱动程序之间,没有同步访问单个设备扩展的安全方法。 ▪ 实现这样一种后门通信方案的一对驱动程序不能单个地被升级,不改变现存的驱 动程序资源不能在它们之间插入中间驱动程序,并且从一个 Windows 平台到下一 个不能被容易地重编辑和移植。 为了保有从一种 Windows 平台或者版本到下一个版本与低层驱动程序的互操作性能,较高 层驱动程序必须重用给它们的 IRP,或者必须创建新的 IRP,并且它们必须使用 IoCallDriver 把请求传送到较低层驱动程序。 3.2.2 创建设备对象和设备扩展 当每个驱动程序调用 IoCreateDevicc 创建设备对象时,驱动程序在 DeviceExtensionSize 参数中指定了设备扩展的大小。 大多数驱动程序仅仅从 AddDevice 例程调用 IoCreateDevice。对于一些驱动程序,例如必 须对驱动器布局 IOCTL 作出响应的磁盘驱动程序,这个调用也能由 Dispatch 例程来作。 除了某些为驱动程序(它与创建设备对象的类驱动程序成对)之外,所有的驱动程序必须 从其 AddDevice 例程调用 IoCreateDevicc 一次或多次,以创建设备对象,这个设备对象代 表它为其处理 I/O 请求的每一种物理、逻辑、或者虚拟设备。否则,对于驱动程序没有为 其创建设备对象的任何目标设备,驱动程序将不能为这些设备获取 IRP。 除设备扩展的大小之外,IoCreateDevice 要求确定以下参数:
系统定义常量,表明设备对象代表的 DeviceType。对于系统定义的 FILE DEVICE XXX 常量列表,分别参见DDK或者WDM控制头文件, ntddk.h或者wdmh 个或多个作过“或”(OR)操作的、系统定义的常量,表明一定类型的设备(软 盘、CD-ROM、和WRM可拆卸媒介设备)的 Device characteristics。否则,被传递到 IoCreateDevice的 Device characteristics值必须是零 指明设备对象的 Flags中的一位是否被置为 DO EXCLUSIVE的布尔值( Exclusive,专 有权),表明驱动程序服务于一个专有设备,例如视频、串行、并行、或声音设备 WM程序必须设置 Exclusize为 FALSE。 指向 DriverOb ject的指针,它被输入到驱动程序的 AddDevice例程,将驱动程序对象 与调用者为其处理IRP的物理、逻辑、或者虚拟设备相联系 一个可选的指向零结尾的 Unicode串( DeviceName)的指针,这个 Unicode串为设备 命名。PnP驱动程序不应该提供设备名称;这样做绕过了PnP管理器的安全特性。PnP 驱动程序能通过调用 loAttachDeviceToDevicestack把自己链到一个没有名称的设备 的驱动程序。 如果用户模式组件需要一个到设备的符号连接,注册设备接口(参见 loRegisterDeviccInterface)。如果内核模式组件需要传统的设备名称,驱动程序必 须命名设备对象,但是命名是不被推荐的。 对于除文件系统驱动程序(FSD)之外的所有驱动程序,I/0管理器也为每个对 IoCreateDevice的成功调用创建一个相关的设备队列对象,如图3.2。与设备对象相关的 设备队列对象在驱动程序被装载之后,描述一个发往 Startle例程的IRP队列。管理它们 自己内部IRP队列的驱动程序,例如系统软盘控制器驱动程序(参见第3部分),不使用 与它们的设备对象有关的设备队列。 驱动程序也能创建附加的设备队列对象。更多的信息参见“设备队列对象和互锁队列” 如果对 IoCreateDevice的调用成功,I/0管理器为设备对象自己和与设备对象有关的所有 其他数据结构提供存储,包括驱动程序设备扩展,它用零初始化 如果调用者创建多个设备对象,I/0管理器通过在设备对象中维护 NextDevice指针,连接 其后其创建的设备对象到输入驱动程序对象。 3.2.3初始化驱动程序特定的设备对象和设备扩展 IoCreateDevice返回后,给调用者一个指向 DeviceOb ject的指针, DeviceOb ject中包含 了指向 Deviceextension的指针,驱动程序必须为它们各自的物理,逻辑,和/或者虚拟设 备在设备对象中创建一定的域 loCreateDcvice设置新创建的设备对象的 Stacksize域为一。最低层驱动程序可以忽略这 个域。当一个较高层驱动程序调用 IoAttachDeviceToDeviceStack把自己附加到相邻的较 低层的驱动程序上时,程序自动地设置设备对象中的域 Stacksize为相邻的较低层驱动程 序的那个值加一。然而,对于某些设备类型,较高层驱动程序可能需要设置域 Stacksize 为较大的值,就像在设备特定文档中说明的那样。设置栈大小保证发送到较高层驱动程序 的IRP将包含驱动程序特定的I/O栈位置,加上当前I/0栈位置的数得到链中所有较低层 驱动程序。 IoCreateDevice设置新创建的设备对象的 AlignmentRequiremcnt域为处理器的数据高速 缓存线的大小减一,以保证用于直接I/0的缓冲区被正确地排列。 IoCreateDevice返回之 后,最低层物理设备驱动程序必须做如下步骤:
33 ▪ 系统定义常量,表明设备对象代表的 DeviceType。对于系统定义的 FILE_DEVICE_XXX 常量列表,分别参见 DDK 或者 WDM 控制头文件,ntddk.h 或者 wdm.h。 ▪ 一个或多个作过“或”(OR)操作的、系统定义的常量,表明一定类型的设备(软 盘、CD-ROM、和 WORM 可拆卸媒介设备)的 DeviceCharacteristics。否则,被传递到 IoCreateDevice 的 DeviceCharacteristics 值必须是零。 ▪ 指明设备对象的 Flags 中的一位是否被置为 DO_EXCLUSIVE 的布尔值(Exclusive,专 有权),表明驱动程序服务于一个专有设备,例如视频、串行、并行、或声音设备。 WDM 程序必须设置 Exclusize 为 FALSE。 ▪ 指向 DriverObject 的指针,它被输入到驱动程序的 AddDevice 例程,将驱动程序对象 与调用者为其处理 IRP 的物理、逻辑、或者虚拟设备相联系。 ▪ 一个可选的指向零结尾的 Unicode 串(DeviceName)的指针,这个 Unicode 串为设备 命名。PnP 驱动程序不应该提供设备名称;这样做绕过了 PnP 管理器的安全特性。PnP 驱动程序能通过调用 IoAttachDeviceToDeviceStack 把自己链到一个没有名称的设备 的驱动程序。 如果用户模式组件需要一个到设备的符号连接,注册设备接口(参见 IoRegisterDeviccInterface)。如果内核模式组件需要传统的设备名称,驱动程序必 须命名设备对象,但是命名是不被推荐的。 对于除文件系统驱动程序(FSD)之外的所有驱动程序,I/O 管理器也为每个对 IoCreateDevice 的成功调用创建一个相关的设备队列对象,如图 3.2。与设备对象相关的 设备队列对象在驱动程序被装载之后,描述一个发往 StartIo 例程的 IRP 队列。管理它们 自己内部 IRP 队列的驱动程序,例如系统软盘控制器驱动程序(参见第 3 部分),不使用 与它们的设备对象有关的设备队列。 驱动程序也能创建附加的设备队列对象。更多的信息参见“设备队列对象和互锁队列”。 如果对 IoCreateDevice 的调用成功,I/O 管理器为设备对象自己和与设备对象有关的所有 其他数据结构提供存储,包括驱动程序设备扩展,它用零初始化。 如果调用者创建多个设备对象,I/O 管理器通过在设备对象中维护 NextDevice 指针,连接 其后其创建的设备对象到输入驱动程序对象。 3.2.3 初始化驱动程序特定的设备对象和设备扩展 IoCreateDevice 返回后,给调用者一个指向 DeviceObject 的指针,DeviceObject 中包含 了指向 DeviceExtension 的指针,驱动程序必须为它们各自的物理,逻辑,和/或者虚拟设 备在设备对象中创建一定的域。 IoCreateDcvice 设置新创建的设备对象的 StackSize 域为一。最低层驱动程序可以忽略这 个域。当一个较高层驱动程序调用 IoAttachDeviceToDeviceStack 把自己附加到相邻的较 低层的驱动程序上时,程序自动地设置设备对象中的域 StackSize 为相邻的较低层驱动程 序的那个值加一。然而,对于某些设备类型,较高层驱动程序可能需要设置域 StackSize 为较大的值,就像在设备特定文档中说明的那样。设置栈大小保证发送到较高层驱动程序 的 IRP 将包含驱动程序特定的 I/O 栈位置,加上当前 I/O 栈位置的数得到链中所有较低层 驱动程序。 IoCreateDevice 设置新创建的设备对象的 AlignmentRequiremcnt 域为处理器的数据高速 缓存线的大小减一,以保证用于直接 I/O 的缓冲区被正确地排列。IoCreateDevice 返回之 后,最低层物理设备驱动程序必须做如下步骤:
1.从设备的排列请求中减 2.把步骤1的结果与设备对象 Alignment Requirement的当前值相比较 3.如果设备排列请求较大,设置 Alignment Requirement为步骤1的结果。否则,将 Alignment Requirement值作为 loCreateDevice的设置值 在任何较高层驱动程序通过调用 loGetDeviceObjectPointer把自己链到另一个驱动程序之 后,较高层驱动程序必须设置它的新创建的设备对象的 Alignment Requirement域为相邻的 较低层驱动程序的设备对象的那个值。作为一般规则,一个较高层驱动程序不应该改变这 个值。如果较高层驱动程序调用 IoAttachDevice或者 LoAt tachDeviceToDevice,那些例 程自动的设置设备对象中的 AlignmentRequirement域为较低层驱动程序的设备对象的那个 值。 loGetDeviceOb ject Pointer返回指向较低层驱动程序的设备对象和指向相关的文件对象的 两个指针。只有FSD(或者可能是另一个最高层驱动程序)能使用返回的文件对象指针。 调用 loGetDeviceObjectPointer的中间驱动程序保存这个文件对象指针,从而使得当驱动 程序被卸载时,它能通过调用 ObDereferenceOb ject被间接引用 在FSD安装包含文件对象(它代表一较低层驱动程序的设备对象)的卷后,中间驱动程序 不能通过调用 LoAttachDevice或者 LoAttachDeviceToDeviceStack把自己链到文件系统和 较低层驱动程序之间。 中间层或者最低层驱动程序也在设备对象的 Flags中设置一个位,方法是将它与 DO DIRECT I0或者是与 DO BUFFERED IO作“或”操作。如果驱动程序作者决定有关的附 加工作将以较好的驱动程序性能为代价,对于缓冲或者直接I/O的,逻辑或者虚拟的设备 的最高层驱动程序能避免设置 Flags。中间驱动程序必须创建其设备对象的 Flags域以匹 配相邻的较低层驱动程序的设备对象。 设置设备对象的 Flags域为 DO DIRECT I0或 DO BUFFERED I0,决定I/0管理器如何在所 有的后来发送到驱动程序的数据传输请求中传递访问到用户缓冲区 然后驱动程序能在设备对象中设置任何其他的依赖设备的值。例如,可拆卸媒介设备的非 WDM驱动程序在I/0操作期间检测到(或怀疑)媒介中的变化,它必须使用 DO_ VERIFY VOLUME与设备对象的 Flags成员作“或”操作。(更多信息参见第16章中 处理可删除的媒介”。)要求突发( inrush)电源的设备的驱动程序必须用 0_ POWER_ INRUSH与 Flags成员做“或”操作,并且不在系统页路径上的设备的驱动程序 必须用D0O_ POWER_ PAGABLE与 Flags成员做或操作。PmP功能和过滤器驱动程序必须清除 DO DEVICE INITIALIZING标记。 在驱动程序初始化设备对象之后,它也能初始化任何内核定义的对象和它在设备扩展中为 其提供存储的其他系统定义的数据结构。驱动程序具体什么时候执行这些仼务依赖于其设 备、对象的类型、和/或数据的性质。简言之,能持续经过PP开始和停止请求的仼何对象 或者数据结构能在 AddDevice例程中被初始化。那些要求以 PnP IRP MN STaRt dEVICe请 求予以提供的资源信息的对象和数据结构,或者那些当设备被停止和/或重新启动时可能要 求变化的对象和数据结构,应该在驱动程序处理 IRP MN START DEVICE请求时被初始化 324设置用户缓冲区的访问 大多数最低层和所有中间层驱动程序在 DeviceOb ject-> Flags中设置一个位,如图3.2所 示,方法是使用 DO BUFFERED I0或者 DO DIRECT I0与他们创建的每个设备对象中的
34 1. 从设备的排列请求中减一。 2. 把步骤 1 的结果与设备对象 AlignmentRequirement 的当前值相比较。 3. 如果设备排列请求较大,设置 AlignmentRequirement 为步骤 1 的结果。否则,将 AlignmentRequirement 值作为 IoCreateDevice 的设置值。 在任何较高层驱动程序通过调用 IoGetDeviceObjectPointer 把自己链到另一个驱动程序之 后,较高层驱动程序必须设置它的新创建的设备对象的 AlignmentRequirement 域为相邻的 较低层驱动程序的设备对象的那个值。作为一般规则,一个较高层驱动程序不应该改变这 个值。如果较高层驱动程序调用 IoAttachDevice 或者 IoAttachDeviceToDevice,那些例 程自动的设置设备对象中的 AlignmentRequirement 域为较低层驱动程序的设备对象的那个 值。 IoGetDeviceObjectPointer 返回指向较低层驱动程序的设备对象和指向相关的文件对象的 两个指针。只有 FSD(或者可能是另一个最高层驱动程序)能使用返回的文件对象指针。 调用 IoGetDeviceObjectPointer 的中间驱动程序保存这个文件对象指针,从而使得当驱动 程序被卸载时,它能通过调用 ObDereferenceObject 被间接引用。 在 FSD 安装包含文件对象(它代表一较低层驱动程序的设备对象)的卷后,中间驱动程序 不能通过调用 IoAttachDevice 或者 IoAttachDeviceToDeviceStack 把自己链到文件系统和 较低层驱动程序之间。 中间层或者最低层驱动程序也在设备对象的 Flags 中设置一个位,方法是将它与 DO_DIRECT_IO 或者是与 DO_BUFFERED_IO 作“或”操作。如果驱动程序作者决定有关的附 加工作将以较好的驱动程序性能为代价,对于缓冲或者直接 I/O 的,逻辑或者虚拟的设备 的最高层驱动程序能避免设置 Flags。中间驱动程序必须创建其设备对象的 Flags 域以匹 配相邻的较低层驱动程序的设备对象。 设置设备对象的 Flags 域为 DO_DIRECT_IO 或 DO_BUFFERED_IO,决定 I/O 管理器如何在所 有的后来发送到驱动程序的数据传输请求中传递访问到用户缓冲区。 然后驱动程序能在设备对象中设置任何其他的依赖设备的值。例如,可拆卸媒介设备的非 WDM 驱动程序在 I/O 操作期间检测到(或怀疑)媒介中的变化,它必须使用 DO_VERIFY_VOLUME 与设备对象的 Flags 成员作“或”操作。(更多信息参见第 16 章中 “处理可删除的媒介”。)要求突发(inrush)电源的设备的驱动程序必须用 DO_POWER_INRUSH 与 Flags 成员做“或”操作,并且不在系统页路径上的设备的驱动程序 必须用 DO_POWER_PAGABLE 与 Flags 成员做或操作。PnP 功能和过滤器驱动程序必须清除 DO_DEVICE_INITIALIZING 标记。 在驱动程序初始化设备对象之后,它也能初始化任何内核定义的对象和它在设备扩展中为 其提供存储的其他系统定义的数据结构。驱动程序具体什么时候执行这些任务依赖于其设 备、对象的类型、和/或数据的性质。简言之,能持续经过 PnP 开始和停止请求的任何对象 或者数据结构能在 AddDevice 例程中被初始化。那些要求以 PnP IRP_MN_START_DEVICE 请 求予以提供的资源信息的对象和数据结构,或者那些当设备被停止和/或重新启动时可能要 求变化的对象和数据结构,应该在驱动程序处理 IRP_MN_START_DEVlCE 请求时被初始化。 3.2.4 设置用户缓冲区的访问 大多数最低层和所有中间层驱动程序在 DeviceObject->Flags 中设置一个位,如图 3.2 所 示,方法是使用 DO_BUFFERED_IO 或者 DO_DIRECT_IO 与他们创建的每个设备对象中的
Flags做或操作。对于数据传递,驱动程序必须使用下列的三种方法之一来访问用户缓冲 服务于一种交互式(缓慢)的设备或一次通常传输比较少的数据的驱动程序应该创建 它的设备对象,以请求缓冲I/0。为小而交互式的传递使用缓冲I/0会全面地改进物 理内存的使用,因为内存管理器不必为每个传输锁定整个物理页,而直接I/0通常是 这样做的。一般来说,视频、键盘、鼠标、串口,和并口的驱动程序请求缓冲I/O。 任何在这样一种设备驱动程序之上的中间层驱动程序必须创建它的 DeviceOb ject Flags,以匹配相邻的较低层驱动程序的那个值。 服务于一次能传递大量数据的设备的驱动程序应该创建它的设备对象,以请求直接 I/O。通过减少常规的中断和最小化缓冲I/0固有的内存分配和拷贝操作,对大批量数 据传递使用直接I/0将提高驱动程序性能。一般地,海量存储设备驱动程序对传输请 求要求直接I/0,包括使用DMA或者PI0的最低层驱动程序,还有任何链在它们之上 的中间层驱动程序。 甚至请求直接I/0的驱动程序使用缓冲I/0满足一定的IRP。尤其是,驱动程序能对 某种系统定义的、设备类型特定的I/O控制代码使用缓冲I/0,这些控制代码是对要 求数据传输的 RP MJ DEVICE CONTROL请求的,而不论驱动程序是否已经用 DO DIRECT I0与它的设备对象的 Flags做或操作。设备控制请求的缓冲方法由设备控 制代码自己确定。 任何在这样一种设备驱动程序之上的中间层驱动程序层必须创建它的 DeviceOb ject Flags,以匹配相邻的较低层驱动程序的那个值。 在创建一个设备对象时,一直在原始的、用户模式的线程(它请求一个I/0操作)的 环境中被调用的驱动程序既不要求直接Ⅰ/0也不要求缓冲1/O。这样一种驱动程序必 须是最高层的驱动程序 下面的小节描述如何使用 DO BUFFERED IO、 DO DIRECT I0与设备对象的 Flags做“或”操 作,或者也不影响发往驱动程序的数据传输请求,包括数据在物理内存中如何被存储,以 及驱动程序如何访问那个内存 324.1使用缓冲Ⅳo 图3.4说明I/0管理器如何创建一个对驱动程序请求传输操作的IRP,这个驱动程序使用 00 BUFFERED IO与它们的设备对象的 Flags做“或”操作 图3.4对用户缓冲区的缓冲I/0 图3.4显示驱动程序已经用D0 BUFFERED I0与设备对象的 Flags做“或”操作时,驱动程 序如何为读请求使用IRP中的 SystemBuffer指针以传递数据。循环中的序号解释如下 1.用户空间的虚拟地址的某些范围代表当前线程的缓冲区,并且缓冲区的内容可以存储 在基于页的物理地址(图3.4中黑暗部分)的范围之内的某一地方 2.I/0管理器为当前线程的读请求服务,线程传递给它一个代表缓冲区的用户空间虚拟 地址的范围 3.I/0管理器检查用户提供的缓冲区的可访问性,并且调用 ExAllocatePool以创建一个 常驻的系统空间缓冲区( SystemBuffer),大小为等于用户提供缓冲区。 4.I/0管理器提供机会访问IRP中的新分配的 SystemBuffer,这个IRP是它发送给驱动 程序的
35 Flags 做或操作。对于数据传递,驱动程序必须使用下列的三种方法之一来访问用户缓冲 区: ▪ 服务于一种交互式(缓慢)的设备或一次通常传输比较少的数据的驱动程序应该创建 它的设备对象,以请求缓冲 I/O。为小而交互式的传递使用缓冲 I/O 会全面地改进物 理内存的使用,因为内存管理器不必为每个传输锁定整个物理页,而直接 I/O 通常是 这样做的。一般来说,视频、键盘、鼠标、串口,和并口的驱动程序请求缓冲 I/O。 任何在这样一种设备驱动程序之上的中间层驱动程序必须创建它的 DeviceObject- >Flags,以匹配相邻的较低层驱动程序的那个值。 ▪ 服务于一次能传递大量数据的设备的驱动程序应该创建它的设备对象,以请求直接 I/O。通过减少常规的中断和最小化缓冲 I/O 固有的内存分配和拷贝操作,对大批量数 据传递使用直接 I/O 将提高驱动程序性能。一般地,海量存储设备驱动程序对传输请 求要求直接 I/O,包括使用 DMA 或者 PIO 的最低层驱动程序,还有任何链在它们之上 的中间层驱动程序。 甚至请求直接 I/O 的驱动程序使用缓冲 I/O 满足一定的 IRP。尤其是,驱动程序能对 某种系统定义的、设备类型特定的 I/O 控制代码使用缓冲 I/O,这些控制代码是对要 求数据传输的 RP_MJ_DEVICE_CONTROL 请求的,而不论驱动程序是否已经用 DO_DIRECT_IO 与它的设备对象的 Flags 做或操作。设备控制请求的缓冲方法由设备控 制代码自己确定。 任何在这样一种设备驱动程序之上的中间层驱动程序层必须创建它的 DeviceObject- >Flags,以匹配相邻的较低层驱动程序的那个值。 ▪ 在创建一个设备对象时,一直在原始的、用户模式的线程(它请求一个 I/O 操作)的 环境中被调用的驱动程序既不要求直接 I/O 也不要求缓冲 I/O。这样一种驱动程序必 须是最高层的驱动程序。 下面的小节描述如何使用 DO_BUFFERED_IO、DO_DIRECT_IO 与设备对象的 Flags 做“或”操 作,或者也不影响发往驱动程序的数据传输请求,包括数据在物理内存中如何被存储,以 及驱动程序如何访问那个内存。 3.2.4.1 使用缓冲 I/O 图 3.4 说明 I/O 管理器如何创建一个对驱动程序请求传输操作的 IRP,这个驱动程序使用 DO_BUFFERED_IO 与它们的设备对象的 Flags 做“或”操作。 图 3.4 对用户缓冲区的缓冲 I/O 图 3.4 显示驱动程序已经用 DO_BUFFERED_IO 与设备对象的 Flags 做“或”操作时,驱动程 序如何为读请求使用 IRP 中的 SystemBuffer 指针以传递数据。循环中的序号解释如下: 1. 用户空间的虚拟地址的某些范围代表当前线程的缓冲区,并且缓冲区的内容可以存储 在基于页的物理地址(图 3.4 中黑暗部分)的范围之内的某一地方。 2. I/O 管理器为当前线程的读请求服务,线程传递给它一个代表缓冲区的用户空间虚拟 地址的范围。 3. I/O 管理器检查用户提供的缓冲区的可访问性,并且调用 ExAllocatePool 以创建一个 常驻的系统空间缓冲区(SystemBuffer),大小为等于用户提供缓冲区。 4. I/O 管理器提供机会访问 IRP 中的新分配的 SystemBuffer,这个 IRP 是它发送给驱动 程序的