对于大多数中间级别和最低级别的驱动程序,设备扩展是与设备对象关联的最重要的数据结构。 其内部结构是驱动程序定义的,通常用于:
维护设备状态信息。
为驱动程序使用的任何内核定义对象或其他系统资源(例如旋转锁)提供存储。
保存驱动程序执行 I/O 操作时必须驻留在系统空间的任何数据。
由于大多数总线、功能和筛选器驱动程序(最低级别和中间驱动程序)在任何当前运行的线程上下文中执行,因此设备扩展是每个驱动程序维护设备状态以及所有其他设备特定数据的主要场所。 例如,任何实现 CustomTimerDpc 或 CustomDpc 例程的驱动程序通常为设备扩展中所需的内核定义的计时器和/或 DPC 对象提供存储。
具有 ISR 的每个驱动程序都必须为指向一组内核定义的中断对象的指针提供存储,并且大多数设备驱动程序将此指针存储在设备扩展中。 每个驱动程序在创建设备对象时确定设备扩展的大小,每个驱动程序定义其自己的设备扩展的内容和结构。
I/O 管理器的 IoCreateDevice 和 IoCreateDeviceSecure 例程从非分页内存池为设备对象和扩展分配内存。
接收 IRP 的每个标准驱动程序例程也会接收一个指针,指向表示所请求 I/O操作的目标设备的设备对象。 这些驱动程序例程可以通过此指针访问相应的设备扩展。 通常, DeviceObject 指针也是最低级别驱动程序 ISR 的输入参数。
下图显示了最低级别驱动程序设备对象的设备扩展的一组具有代表性的驱动程序定义数据。 更高级别的驱动程序不会为 IoConnectInterrupt 返回的中断对象指针提供存储,并且传递给 KeSynchronizeExecution 和 IoDisconnectInterrupt。 但是,如果驱动程序具有 CustomTimerDpc 例程,则更高级别的驱动程序将为下图中显示的计时器和 DPC 对象提供存储。 更高级别的驱动程序还可能为高层旋转锁和互锁工作队列提供存储。
除了为中断对象指针提供存储外,最低级别的设备驱动程序如果其 ISR 需要处理位于不同向量的两个或多个设备的中断,或者拥有多个 ISR,则必须为中断自旋锁提供存储。 有关注册 ISR 的详细信息,请参阅 注册 ISR。
通常,驱动程序将指向其设备对象的指针存储在其设备扩展中,如图所示。 驱动程序还可以在扩展中保留设备的资源列表的副本。
较高级别的驱动程序通常在其设备扩展中存储指向下一个较低驱动程序的设备对象的指针。 更高层的驱动程序必须先在 IRP 中设置下一个较低驱动程序的 I/O 堆栈位置,然后将指向该较低驱动程序的设备对象的指针传递给 IoCallDriver,如 处理 IRP 中所述。
另请注意,为较低级别驱动程序分配 IRP 的任何更高级别的驱动程序都必须指定新 IRP 应具有多少个堆栈位置。 具体而言,如果较高级别的驱动程序调用 IoMakeAssociatedIrp、 IoAllocateIrp 或 IoInitializeIrp,则它必须访问下一级驱动程序的目标设备对象来读取其 StackSize 值,以便为这些支持例程提供正确的 StackSize 作为参数。
尽管较高级别的驱动程序可以通过 IoAttachDeviceToDeviceStack 返回的指针从下一级驱动程序的设备对象读取数据,但此类驱动程序必须遵循以下实现准则:
从不尝试将数据写入较低驱动程序的设备对象。
唯一例外的是文件系统,它们在较低级别的可移动媒体驱动程序设备对象的标志中对 DO_VERIFY_VOLUME 进行设置和清除。
出于以下原因,从不尝试访问较低驱动程序的设备扩展:
无法安全地在两个驱动程序之间同步对单个设备扩展的访问。
一对实现此类后门通信方案的驱动程序不能单独升级,不能在它们之间插入中间驱动程序,而无需更改现有驱动程序源,并且无法从一个 Windows 平台轻松重新编译和移动到下一个 Windows 平台。
为了保持与较低级别的驱动程序的互作性,从一个 Windows 平台或版本到另一个版本,高级驱动程序要么必须重复使用给定的 IRP,要么必须创建新的 IRP,并且必须使用 IoCallDriver 将请求传达给较低级别的驱动程序。