内核模式驱动框架(KMDF)是开发内核模式驱动的基础框架。它提供C语言设备驱动接口(DDI),能用来创建Microsoft® Windows® 2000及以后版本的驱动。本质上,框架就是一个设备驱动架构,可以定制为特定设备。KMDF实现代码来处理常见的驱动需求。驱动定制框架,设置对象属性,注册用来通知重要事件的回调函数,以及包含符合设备特性的代码。
KMDF提供了良好的对象模型,控制对象的生命周期和内存分配。对象以父/子模型的层次组织,而重要的驱动数据结构由KMDF维护而不是驱动。
本文介绍了KMDF的结构和特性,使用KMDF驱动的要求(有时候称为KMDF基础的驱动或简称为KMDF驱动)。需要对Windows操作系统和I/O模型有基本了解。
Windows驱动基础(WDF)也包括了用户模式驱动框架(UMDF)。如果你的设备不处理中断,不直接内存访问(DMA),也不需要其他内核模式资源,如非分页池内存,你应该考虑写用户模式的驱动。具体见“Introduction to the WDF User-Mode Driver Framework”,在资源中列出。
KMDF支持的设备
KMDF设计用来取代Windows驱动模型(WDM)。KMDF的初始版本支持WDM的大多数设备和设备类型,除了当前由迷你端口模型支持的设备。表1列出了KMDF支持的设备和驱动类型。
表格 1. KMDF支持的设备和驱动类型
设备或驱动类型 | 现有的设备模型 | 说明 |
控制和非PNP驱动 | 过时 | 支持 |
IEEE 1394 客户驱动 | 取决于设备类别 | 支持和现有设备类别规范不一致的设备 |
ISA,PCI,PCMCIA,和安全数字 (SD) 设备 | WDM 驱动 | 如果设备类别或端口驱动不支持驱动派遣函数,则支持 |
NDIS 协议驱动 | WDM 上层 和NDIS下层 | 支持 |
NDIS WDM 驱动 | NDIS 上层 和WDM 下层 | 支持 |
SoftModem 驱动 | 上层支持TAPI接口的WDM驱动 | 支持 |
存储类驱动和过滤驱动 | WDM驱动 | 支持 |
传输驱动程序接口 (TDI)客户驱动 | 通用 WDM 驱动 | 支持 |
USB 客户驱动 | 取决于设备类别 | 支持 |
Winsock 客户驱动 | 有设备特定请求的回调函数接口的WDM驱动 | 支持 |
总体上,KMDF支持和WDM一致的驱动,为主要的I/O派遣例程提供入口,以及处理I/O请求包(IRPs)。对一些设备类型,设备类和端口驱动提供驱动派遣函数并回调迷你端口驱动来处理特定I/O细节。这样的迷你端口驱动本质上就是回调函数库,现在不被KMDF支持。此外KMDF也不支持使用Windows图像处理架构的设备类型。
KMDF 组件
KMDF分布在Windows驱动工具(WDK)中,由头文件,库文件,样例驱动,开发工具,公共调试符号,和跟踪格式文件。KMDF默认安装在WDK安装根目录的WDF子目录下。KMDF基础的驱动在WDK环境下构建。表2列出了KMDF作为WDF一部分安装的组件。
表格 2. KMDF 组件
组件 | 位置 | 描述 |
头文件 | wdf/inc | Header files required to build KMDF drivers构建KMDF驱动需要的头文件 |
库文件 | wdf/lib | x86,x64,和 Intel Itanium 结构的库文件 |
样例驱动 | wdf/src | 大量设备类型的样例驱动;多数是从Windows驱动开发工具(DDK)WDM样例移植的 |
工具 | wdf/bin | 测试,调试,和安装驱动的工具;包括了可再发行的KMDF辅助安装程序,WdfCoinstallernn.dll |
调试符号 | wdf/symbols | 公共符号数据库(.pdb)文件,用于KMDF库文件以及checked和free版本辅助安装程序 |
跟踪格式文件 | wdf/tracing | 跟踪格式文件,用于跟踪KMDF库和辅助安装程序产生的跟踪消息 |
为了帮助调试,KMDF分布于free和checked运行时库构建,加载器以及相应符号。但是,Microsoft不提供checked版本的可再发行辅助安装程序。
KMDF 驱动结构
KMDF驱动组成:DriverEntry函数定义驱动以KMDF为基础,让驱动能够响应设备相关事件的一套回调函数供KMDF调用,以及驱动特定的功能函数。几乎所有KMDF驱动都必须包含以下内容:
· DriverEntry函数,代表了驱动的主函数入口。
· EvtDriverDeviceAdd回调函数,当PNP管理器枚举驱动设备时调用(对于非PNP设备驱动不是必须的)。
· 一个或多个 EvtIo* 回调函数,处理特定队列中特定类型的I/O 请求。
典型地,驱动创建一个或多个队列,KMDF放入给驱动设备的I/O请求。驱动可以配置请求的类型和请求派遣的方式。细节参见后面的“KMDF I/O模型 ”。
一个简单设备的最小内核模式驱动,可能仅包含这些功能。KMDF包含了代码支持默认的电源管理和PNP操作,所以不使用物理硬件的驱动可以省去大部分PNP和电源管理代码,比如向设备栈下层传递电源IRP。设备支持越多的特定功能,驱动提供越多的功能,驱动需要的代码就越多。
KMDF和 WDM 驱动的比较
KMDF模型让驱动比WDM的驱动更简洁,更容易调试。KMDF驱动需要最少的代码提供默认操作,因为大部分代码位于框架中,已经全面测试并可以全球性地升级。
因为KMDF事件经过明确严格的定义,KMDF基础的驱动代码一般都不复杂。每个驱动回调函数都被设计为完成特定任务。所以,和WDM驱动比较,KMDF基础的驱动代码量更少,更确切地说,没有状态变量和锁。
作为WDF开发的一部分,微软已经把Windows DDK中的样例驱动从WDM驱动转换为KMDF驱动。无一例外地,KMDF驱动更小更简单。
表格3说明了PCIDRV,Serial,和 OSRUSBFX2驱动“前后”统计。
表格3. WDM-KMDF 样例驱动统计
统计 | PCIDRV1 | Serial2 | OSRUSBFX23 | |||
WDM | KMDF | WDM | KMDF | WDM | KMDF | |
代码总行数 | 13,147 | 7,271 | 24,000 | 17,000 | 16,350 | 2,300 |
PNP和电源管理的代码行数 | 7,991 | 1,795 | 5,000 | 2,500 | 8,700 | 742 |
锁和同步原语 | 8 | 3 | 10 | 0 | 9 | 0 |
PNP和电源管理的状态变量 | 30 | 0 | 53 | 0 | 21 | 0 |
1 PCIDRV样例支持Intel E100B NIC板卡。WDM和KMDF版本功能是相同的。
2 Serial样例支持串行设备。本例中,WDM样例支持多设备,但是KMDF样例只支持单端口。然而WDM驱动的统计没有包括用来支持多设备的代码,锁或者变量,所以两者的统计可以相互比较。
3 OSRUSBFX2 样例支持OSR 的USB-FX2主板。WDM和KMDF版本功能是相同的。WDM版本可以从http://www.osronline.com获得。
表格说明,驱动从WDM转换为KMDF后,代码量显著减少——特别是PNP和电源管理。而且KMDF样例需要的锁,同步原语和状态变量更少。
· 代码量。KMDF驱动整体上以及执行PNP和电源管理需要的代码都显著减少。更少的代码意味着更简单的驱动,以及更少的错误概率和更小的可执行文件。
· 锁和同步原语。在所有的三个例子中,KMDF驱动不仅更小,锁和同步原语的数量也显著减少。这点很重要,因为它消除了常见驱动问题的来源。WDM驱动用所来同步I/O队列以及PNP和电源操作,也经常用锁来管理I/O的取消操作。用锁的方法一般会引入一个或多个竞争情况,还很难正确执行。KMDF驱动几乎不使用锁,因为需要的锁由框架提供了。
· 状态变量。PNP和电源管理所需要的状态变量数量可以用来衡量其驱动执行的复杂程度。WDM驱动以IRP形式从系统中收到PNP和电源管理的请求。当这样的驱动收到IRP时,它必须确定当前设备状态和系统状态,并以此为基础,决定执行什么操作来完成IRP。一些IRP从设备栈上层传递下来时,驱动必须立即处理,而另一些IRP,驱动必须等到它们在下层设备栈中完成后再处理。这样的结果就是,WDM驱动必须保存大量有关设备当前状态以及PNP和电源管理请求的信息。保存这些信息,WDM PCIDRV样例需要30个变量,Serial样例需要53个变量,而OSRUSBFX2样例需要21个。
KMDF版本的三个样例驱动不需要状态变量。KMDF驱动不用维护这些信息,因为框架代替它们完成了。框架实现了庞大的状态机,结合了PNP和电源管理操作以及I/O操作。驱动仅当它需要操作时提供回调函数。比如,一个提供唤醒信号的设备,它的驱动可以注册一个回调函数来获取信号,然后KMDF在合适的时机调用它。相比较,WDM驱动则必须确定哪些电源管理IRP需要它来获取信号,并且应该在处理IRP的什么时候来做。
每个驱动创建一个或多个设备对象,代表了在处理I/O请求和管理设备时的驱动角色。KMDF支持以下设备类型的开发:
· 过滤设备对象(filter DOs)代表了过滤驱动的角色。Filter DOs “过滤”或者修改了目标设备上一种或多种类型的I/O请求。Filter Dos连接到PNP设备栈。
· 功能设备对象(FDOs)代表了功能驱动的角色,它是设备的主驱动。FDOs连接到PNP设备栈。
· 物理设备对象(PDOs)代表了总线驱动的角色,它枚举子设备。PDOs连接到PNP设备栈。
· 控制设备对象代表了旧的非PNP设备或者控制接口。它们不属于PNP设备栈。
根据设备的设计和设备栈中的其他驱动,一个驱动可以担任一个或多个角色。每个PNP设备有一个功能驱动和一个总线驱动,但是可以有任意个过滤驱动。在PNP设备栈中,驱动有时候作为一个设备的功能驱动,有时候作为它的设备枚举的子设备的总线驱动。比如,USB hub驱动作为自己的功能驱动,而且是连接到hub上的每个USB设备的总线驱动。所以,它为hub创建FDO,为每个连接USB设备创建PDO。
下面部分提供更多关于KMDF支持的驱动类型和设备对象的信息。
过滤驱动和过滤设备对象
过滤驱动接收以其设备为目标的一种或多种类型I/O请求,根据请求执行相应动作,然后向栈的下层驱动传递请求。过滤驱动一般自己不完成设备I/O;相反,他们修改或者记录由其他驱动来完成的请求。设备特定数据的加密 /解密软件通常由过滤驱动实现。
过滤驱动向PNP设备栈中加入filter DO。当设备加入系统时,KMDF驱动通知框架它是过滤驱动,所以KMDF创建filter DO并进行适当默认配置。
大多数过滤驱动不对给其设备的所有请求“感兴趣”;过滤驱动可能只过滤读请求或者创建请求。为了简化过滤驱动的实现,KMDF只派遣过滤驱动指定类型的请求,而把其他请求传递到设备栈下层。过滤驱动不会收到别的请求,所以不用检查它们或者把它们交给其他驱动。
KMDF不支持总线过滤驱动的开发。
功能驱动和功能设备对象
功能驱动是设备的主要驱动。功能驱动和设备通信来操作I/O,并且一般会管理设备的电源策略。在PNP设备栈中,功能驱动是FDO。
为了支持功能驱动,KMDF包含了FDO接口,其中定义了一套方法,事件,以及应用于FDOs初始化和操作的属性。通过FDO接口,驱动可以:
· 注册和设备资源分配相关的事件回调函数。
· 获取物理设备的属性。
· 打开注册表键值。
· 如果设备枚举了一个或多个子设备,那么管理子设备列表。
当驱动创建了设备对象,KMDF会创建一个FDO除非驱动指定其他情况。
默认地,KMDF假定功能驱动管理自己设备的电源策略。如果设备支持唤醒信号,功能驱动一般会设置电源策略事件回调函数来实现该功能。
总线驱动和物理设备对象
总线驱动作为父设备的功能驱动,枚举一个或多个子设备。父设备可以是总线,也可以是多功能设备,其枚举的子设备功能需要不同类型的驱动。在PNP设备栈中,总线驱动是PDO。
和FDO一样,KMDF为PDO定义了特定的方法,事件和属性。通过PDO接口,驱动可以:
· 注册事件回调函数,让驱动可以报告子设备需要的硬件资源。
· 注册与设备锁定以及弹出相关的事件回调函数。
· 注册事件回调函数,进行总线水平的操作,使子设备能够触发唤醒信号。
· 给子设备分配PNP,兼容性以及实例ID。
· 给子设备设置移除和弹出关系。
· 通知系统子设备已经弹出或者意外移除。
· 获取和更新子设备的总线地址。
· 指示驱动控制原始设备。(原始设备由总线驱动直接控制,没有功能驱动)
为指明总线驱动,KMDF驱动在创建设备对象前调用一个或多个PDO初始化方法。如果驱动指示它是一个原始设备,KMDF则假定驱动管理设备的电源策略。
用KMDF写总线驱动比WDM简单得多。KMDF代替驱动管理PDO的状态,所以驱动只需要在设备添加或移除时通知KMDF。KMDF支持动态和静态模型来枚举子设备。如果子设备状态很少改变,总线驱动应该使用静态模型。动态模型支持设备驱动类似IEEE 1394总线,其子设备的状态可能随时改变。
对于总线驱动,KMDF处理枚举的大部分细节,包括:
· 向WDM报告子设备。
· 协调子设备扫描。
· 维护子设备列表。
此外,驱动用来报告资源需求的KMDF接口比WDM提供的更容易使用。
旧设备驱动和控制设备对象
除了PNP功能,总线和过滤驱动之外,KMDF支持旧设备驱动的开发,但是它们不由PNP生命周期模型控制。此类驱动创建控制设备对象,其不是PNP设备栈的一部分。
PNP驱动也可以使用控制设备对象来实现脱离设备栈的控制接口。应用程序能绕过栈中其他驱动的任何过滤操作,直接向控制设备对象发送请求。这样的控制设备对象一般有一个队列,有时会转发队列中的请求到PNP设备对象。
因为控制设备对象不是PNP设备栈的一部分,所以驱动必须在初始化完成时通知KMDF。此外,驱动必须在设备移除时自己删除设备对象,因为只有驱动知道如何控制这样一个设备的生命周期。