PDF全文下载地址:http://download.csdn.net/source/2320280
驱动网介绍:http://bbs3.driverdevelop.com/read.php?tid-120461.html
一. WDF 简介
WDF的特点
微软刚开始设计WDF的时候,准备搞一套新的Miniport驱动,如果真是这样的话,那么系统中可能又多了一个WDF Miniport,就好象NDISMiniport一样。但好在不少人提出反对意见:Miniport驱动越来越多,驱动越来越难写。有人就以NDIS为例,在NDIS中想获取并处理IRP是一件难事,而很多时候,人们又急切地想获取IRP对象,这就成为一件极其痛苦的事情。另外一件痛苦是,某个微端口被推出后,往往只对较新的系统给予支持,而旧系统则不支持。以AVStream微端口驱动为例,就只支持XP及以后的系统,如果要在Win 2000上安装AVStream驱动,则非常麻烦。这使得编程人员难以实现驱动程序的多平台支持。
综合分析了各种微端口的缺点,大家最后得到了几个共识:
新的驱动框架,应该做到WDM兼容,使得程序员在编程过程中能很方便地在WDF-WDM之间进行切换。
新的驱动框架,应该做到向后兼容。也就是说,WDF推出于Vista系统之后,但它必须向后支持Win XP、2000这些旧的操作系统。
I/O 与队列
WDF的一个主要特点是,所有的东西都被封装成一个对象,而所有的操作都被定义成一个事件(Event)或回调(Callback)。WDF对每个框架对象都维护一个引用计数,这样就能有效地控制对象的生命周期。
举例来说:WDF的一个优点是将I/O进行了封装,使得程序员不用直接面对IRP,而只要处理WDFREQUEST对象,处理也变得异常简单。而I/O的对象不再是设备对象,而是一个新的I/Otarget对象。I/O target对象比DEVICE_OBJECT更广,它不仅可以代表设备,也可以代表驱动,队列等。队列对象是一个全新事务,WDM编程中涉及到队列的地方不多,但WDF却让他成为了不可避免的部件。队列是专门用来管理WDFREQUEST对象的,因为WDFREQUEST对象是对IRP的封装,所以队列归根到底是对IRP的管理。队列允许WDFREQUEST对象以三种方式发送给驱动处理:并行、串行、手动。由于对IRP的管理、使用失措,而导致的驱动问题,是WDM程序的一大难点。现在队列的引入,让IRP从野马变成了驯良的座骑。
由于队列比较重要,下面我们分别来讲一讲。三者中,并行最好理解了,和WDM中的无序方式一样,所有的IRP都是并行处理的,也就是说,收到一个IRP就立即处理一个IRP。串行和手动可能较难理解,下面我们举例演示串行队列的使用。
我们假设有一个用户线程X以同步方式向内核发送一个命令。内核框架收到命令后,判断出命令应当归于串行队列1。此时串行队列1中已经有3个排队命令。
下图中,图1显示了新命令的入队过程;图2显示了命令的处理过程;图3显示了命令完成后的线程、队列状态。
每个设备对象都有一个默认队列,未经特殊说明的请求类型都被送往这个默认队列中,如要把某些请求排列到特殊队列,则需特别说明。一般总是把默认队列设置为并行队列,这是为了将效率最大化。只将部分需特别处理以保证安全的对象,放置到串行队列中。而手动队列一般放置一些特殊的请求,这些请求往往是为了等待某个条件被满足以达到同步目的,或等待需要的数据到来,如从USB的中断端口读取数据。
多个队列之间可以互通生气,就是说,A队列中的请求,可以被送到B队列中进行排队,这种交流存在于任何的两种队列之间。在不少情况下,需要用到队列转移,比如说,IRP_MJ_DEVICE_IO_CONTROL类型的请求,一般都可以被并行处理的,但控制号为CTL_CODE为IOCTL_REQUEST_1的DEVICE_IO_CONTROL请求却必须被串行处理;这时候我们可以把IRP_MJ_DEVICE_IO_CONTROL请求设置为在并行队列中排队,这样绝大部分的控制请求都将被并行处理,而当并行队列的处理函数遇到控制码为IOCTL_REQUEST_1的时候,放弃对它的处理,并重新将他送到串行队列中排队等待。
兼容性
兼容性体现在两个方面,很令人振奋:对WDM的兼容,对旧系统的兼容。
能兼容WDM,或者说能轻松切换到WDM模式是WDF的一大优点。如果不实现这一点,将给很多人带来痛苦。特别是当人们使用某个小端口驱动,将代码写了一大半的时候,忽然发现某个必须用WDM才能完成而难以实现的问题时,将导致天大的痛苦。
在WDF程序中可以很容易获取IRPs, device objects, driver objects这三种对象,并按照WDM方式处理它们。实际上,这就够了。
对旧系统的兼容,这一点一直以来,就不是微软的强项。但WDF却令人刮目相看。虽然在它的第一版被发布的时候Vista系统已经推出,但他却能够支持所有的NT系统,并且所提供的WDF示例代码,能够在所有NT平台上跑。这使得我们可以一个驱动同时支持WIn2000、XP、Vista、Win7这些系统,而不用在代码中对系统进行判断,更不用每个系统各写一个驱动版本。其实很多组件达不到这个要求,让我们看看NDIS、AVStream的表现吧,就有理由回过头来赞赏WDF。
PNP 和电源实现
恐怕这才是最令人高兴的!
几乎没有人能写出PNP/电源处理没有一点问题的驱动代码来。真的,因为实在是太复杂了,分叉路径太多了。WDF为PNP、电源、电源策略三者定义的状态值,据说达到近两百种之多——想要实际领略一下,请在WDK文档中搜索查看WDF_DEVICE_PNP_STATE、WDF_DEVICE_POWER_STATE和WDF_DEVICE_POWER_POLICY_STATE,三者加起来超过了270个枚举量。按照最苛刻的要求,你当然也要在驱动中把这些状态都进行处理,哪怕大部分状态只有百万分之一的几率发生。恐怕没有人曾经把这么多的状态都自己处理过吧——用WDM代码!
其他令人头疼的问题还包括:pending I/O, correct I/O handlingand synchronization, DMA。它们都看似容易,却实在很难被处理的,因为其内部实现的复杂性。我不准备在这里对此内容进行讨论。但值得欣慰的是,WDF将从此让我们好过起来,当遇到上述这些问题的时候——微笑吧,程序员们!蓝屏会远离我们的,而更强壮的驱动代码在朝我们招手。
WDF对电源和PNP的设计方式是,进行最基本的处理,框架向程序提供回调接口,如果程序注册了某个回调,则框架在对应的状态变化时刻调用此回调,否则就使用默认的处理方式。
下面是PNP/Power/Power策略三者的回调函数结构体定义。
typedefstruct _WDF_PNPPOWER_EVENT_CALLBACKS
ULONGSize;// 结构体长度。结构体版本存在升级趋势。
// 初始与结束回调函数。
EvtDeviceD0Entry早于vtDevicePrepareHardware被调用;
EvtDeviceD0Exit晚于
// 进入PFN_WDF_DEVICE_D0_EXITEvtDeviceD0Exit;D0电源状态
PFN_WDF_DEVICE_RELEASE_HARDWAREEvtDeviceReleaseHardware;
// 设备被EvtDeviceSelfManagedIoInit中
PFN_WDF_DEVICE_SELF_MANAGED_IO_CLEANUPEvtDeviceSelfManagedIoCleanup;
REMOVE后,此回调被调用来处理遗留的PFN_WDF_DEVICE_SELF_MANAGED_IO_FLUSH EvtDeviceSelfManagedIoFlush;
D0状态后,系统为驱动创建的每个框架设备对象调用此函数,来申请
PFN_WDF_DEVICE_SELF_MANAGED_IO_INITEvtDeviceSelfManagedIoInit;
// 设备休眠、设备拔除、或者PFN_WDF_DEVICE_SELF_MANAGED_IO_SUSPENDEvtDeviceSelfManagedIoSuspend;
PFN_WDF_DEVICE_SELF_MANAGED_IO_RESTARTEvtDeviceSelfManagedIoRestart;
// 设备被异常拔除
// 查询设备是否能够被移除
// 查询设备是否可被停止
// 当驱动使用一些特殊文件的时候,此回调被调用。
Dump文件
// 系统初始化的时候,或者驱动属下的多个设备对象之间的关系发生变化时,此回调被调用。
}WDF_PNPPOWER_EVENT_CALLBACKS, *PWDF_PNPPOWER_EVENT_CALLBACKS;
// 电源策略回调
typedefstruct _WDF_POWER_POLICY_EVENT_CALLBACKS
ULONGSize;// 结构体长度。
// 设备将进入休眠状态了,尚未离开// 实现电源策略:让设备可以从即将进入的睡眠状态中醒来。
// 设备从休眠状态中被唤醒,进入// 实现电源策略:为了让设备进入休眠后不再醒来,要在这里做一些设置
// 设备自己把自己从休眠状态中被唤醒,此时已经进入// 但// 实现电源策略:能接受并处理唤醒信号。PFN_WDF_DEVICE_WAKE_FROM_S0_TRIGGEREDEvtDeviceWakeFromS0Triggered;
// 下面的三个函数类似前三个,但这里的系统状态是S0下。
Sx唤醒到PFN_WDF_DEVICE_ARM_WAKE_FROM_SXEvtDeviceArmWakeFromSx;
PFN_WDF_DEVICE_WAKE_FROM_SX_TRIGGEREDEvtDeviceWakeFromSxTriggered;
// 是WDF 1.7以后才有。
PFN_WDF_DEVICE_ARM_WAKE_FROM_SX_WITH_REASON
<font style="font-size: 9pt;" color="black" face=""">}WDF_POWER_POLICY_EVENT_CALLBACKS, *PWDF_POWER_POLICY_EVENT_CALLBACKS;
所有的回调函数,都在被动级别上运行,这替我们省去了很多麻烦。这使得程序在进行初始化或反初始化的时候,不用担心运行于分发级别(DISPATCH_LEVEL)而顾忌重重。因为在分发级别上运行的代码有很多限制,常常免不了要使用WorkItem,而使用workitem往往又必须同时实现同步,相当麻烦。
上述的回调函数和WDM的分发函数间,谈不上一一对应,但也存在着比较强的对应关系。比如函数EvtDevicePrepareHardware 对应了WDM中的PNP_START_DEVICE分发;函数EvtDeviceReleaseHardware 对应了WDM中的PNP_STOP_DEVICE分发; 函数EvtDeviceSurpriseRemoval对应了WDM中的PNP_SURPRISE_REMOVE分发; 函数EvtDeviceD0Entry/EvtDeviceD0Exit对应了WDM中的PNP_SET_POWER分发,等等。
在处理PNP_START_DEVICE的时候,免不了要担心一下是第几次调用StartDevice函数来,考虑电源状态是否已进入D0状态。EvtDevicePrepareHardware回调却不必有此顾虑,因为框架已在内部做了考虑。
其他
WDF中包含了两套子框架,即:KMDF和UMDF。KMDF用来编写内核驱动;UMDF用来编写用户层驱动。本章中只涉及KMDF的内容,所以绝大部分情况下,WDF仅指KMDF而言。WDF仍在不断的发展中,四五年的时间里,他得到了越来越多人们的推崇,而对于他的将来,人们还有更多的憧憬。比如希望微软总有一天能够将WDF代码开源(像MFC一样);比如WDF也许有一天会改成用C++来实现,以实现更多的封装、继承特性。等等。