wdf驱动简介


***【在线视频教程】***

好文章,来自【福优学苑@音视频+流媒体】

WDF驱动简介及几个驱动对象介绍


1.1   WDF简述:

WDF是微软提出的全新驱动程序模型,它提供了面向对象、事件驱动的驱动程序开发框架,对它的研究是设计高效稳定设备驱动程序的基础。注意理解WDF模型的特点,对象模型,以及基本结构.


设备驱动程序是硬件设备连接到计算机系统的软件接口,任何设备都必须有相应的驱动程序才能在计算机系统上正常工作。设备驱动程序的优劣直接关系到整个系统的性能和稳定性,因此,设计和开发稳定高效的驱动程序具有重要意义。


WDF(Windows Driver Foundation)是微软提出的下一代全新的驱动程序模型,它是在WDM(Windows Driver Model)的基础上发展而来的,支持面向对象、事件驱动的驱动程序开发,提供了比WDM更高层次抽象的高度灵活、可扩展、可诊断的驱动程序框架。WDF框架管理了大多数与操作系统相关的交互,实现了公共的驱动程序功能(如电源管理、PNP支持),隔离了设备驱动程序与操作系统内核,降低了驱动程序对内核的影响。


WDF提供了两个框架:KMDF(内核模式驱动程序框架)和UMDF(用户模式驱动程序框架)。


 


1.2   WDF对象模型

KMDF框架支持面向对象、事件驱动的驱动程序模型


它定义了一系列的对象用来表示设备、驱动、中断等,每个对象有对应的属性、方法和事件。驱动程序利用这些方法创建对象、设置属性和响应事件。

1. Framework Object Model


image.png


2. KMDF程序结构

KMDF属于即插即用驱动程序的驱动模型:


1.一个DriverEntry例程。

2.一个EvtDriverDeviceAdd例程,类似于WDM的AddDevice例程。

3.一个或多个IO队列。

4.一个活多个IO事件回调例程,类似于WDM的DispatchXxx。

5.支持的即插即用和电源管理回调例程。

6.支持的WMI回调例程,用于管理计算机系统

7.其他回调例程,如对象的清除例程、中断处理例程、DMA例程。


框架定义的主要对象有:


1.2.1 WDFDRIVER对象

对应于WDM中的DRIVER-OBJECT,描述驱动在内存中的实例,包括加载的位置、有关的属性和所管理的设备。


1.2.2 WDFDEVICE对象

对应于WDM中的DEVICE-OBJECT,描述由驱动程序管理的单个设备实


例。设备可以是命名的也可以是未命名的,用户模式程序可以通过设备接口或设备名称访问设备。WDFDEVICE对象具有丰富的属性,如PNP和电源管理相关的事件处理回调函数(callbacks)。


1.2.3 WDFREQUEST对象

对应于WDM中的IRP,表示一个I/O请求。


1.2.4 WDFQUEUE对象

每个WDFQUEUE对象和一个WDFDEVICE对象关联,描述一个特殊的I/O请求队列。它具有一系列的事件处理回调函数,当I/O请求进入队列时,框架将自动调用驱动程序中对应的callback。


1.2.5 WDFINTERRUPT对象

表示设备中断。驱动程序可以通过WDFINTERRUPT对象的中断使能和禁止事件处理callbacks使能或禁止设备中断;通过ISR和DPC for ISR例程处理设备中断。


 


1.3          WDF的对象模型是层次化的模型。

WDFDRIVER对象是根对象,其他对象都是它的子对象。对于大多数对象,驱动程序在创建它们的时候可以指定父对象,如果没有指定,则框架默认其父对象为WDF DRIVER对象。


WDF大大简化了WDM中的PNP和电源管理的开发。WDF框架为设备停止、设备删除、电源状态切换等PNP和电源管理事件提供了适合的缺省行为,驱动程序本身不再纠缠于复杂的PNP和电源管理事件处理。


 


1.4          WDF请求队列

一个设备可以有多个请求队列,每个请求队列可以有一种模式。


1.4.1  WdfIoQueueDispatchSerial模式

请求队列将请求串行化后再处理


1.4.2  WdfIoQueueDispatchParallel模式

自动在每个请求到来时调用相应的回调函数;


1.4.3  WdfIoQueueDispatchManual模式

允许驱动程序手工分发请求,类似于WDM的工作方式。


 


在WDM驱动程序中,I/O请求的取消是一个复杂难以理解的过程,开发人员必须有对内核深刻的理解才能正确处理I/O请求的取消。WDF框架支持内建的I/O请求取消处理,使得驱动程序处理取消I/O请求的工作大大简化。


 


 


1.5           I/O请求包(IRP) 

1.5.1  IRP简介

IRP是I/O系统用来存储处理I/O请求所需信息的地方, I/O管理器在IRP中保存一个指向调用者文件对象的指针。


从编程的角度看, IRP是I/O管理器在响应一个I/O请求时从非分页系统内存中分配的一块可变大小的数据结构内存, I/O管理器每收到一个来自用户的请求就创建一个该结构,并将其作为参数传给驱动程序的DispatchXxx、StartIo等例程。该结构中存放有请求的类型、用户缓冲区的首地址、用户请求数据的长度等信息。驱动程序处理完这个请求后, 也在该结构中添加处理结果的有关信息, 然后调用IoCompleteRequest将其返回给I/O管理器, 用户程序的请求随即返回。     


  每个IRP可以被看成由两部分组成:


 固定部分和一个I/O堆栈。IRP的固定部分包含关于请求的信息, I/O堆栈则包含一系列I/O堆栈单元(I/O Stack location), 单元的数目应与驱动程序堆栈中处理这一请求的驱动程序数目相同, 每个单元对应一个将处理该IRP的驱动程序。     


1.5.2  IRP固定部分的域

 MdlAddress(Memory Descriptor List, MDL): 指向一个内存描述表。当驱动程序使用直接I/O时, MDL用来描述一个与该请求相关联的用户模式缓冲区       AssociatedIrp: 该域是一个三指针联合, 其中与WDM驱动程序相关的指针是AssociatedIrp.SystemBuffer。如果设备执行缓冲I/O, 则SystemBuffer指针指向系统空间缓冲区, 否则为NULL。 


      RequestorMode: 取值为一个枚举常量UserMode或KernelMode, 指定请求初始化的模式为用户模式还是核心模式。驱动程序有时需要查看这个值来决定是否需要信任某些参数 


      Cancel: 该域为BOOLEAN类型。如果为TRUE, 则表明IoCancelIrp已被调用, 该函数用于取消这个请求。如果为FALSE, 则表明没有调用IoCancelIrp函数 


      CancelIrql: 一个IRQL(I/O Request Query Level)值, 表明那个专用的取消自旋锁是在这个IRQL上获取的。当驱动程序在取消例程中释放自旋锁时应该参考这个域 


      CancelRoutine: 指向驱动程序取消例程的地址。应该使用IoSetCancelRoutine函数设置CancelRoutine域而不是直接修改该域  


1.5.3      IRP堆栈单元中的域 

      任何内核模式程序在创建一个IRP时, 同时还创建了一个与之关联的I/O堆栈。堆栈中的I/O堆栈单元由IO_STACK_LOCATION结构定义, 每个堆栈单元都对应一个将处理的IRP的驱动程序。为了在一个给定的IRP中确定当前的IRP I/O堆栈单元, 驱动程序可以调用IoGetCurrentIrpStackLocation函数, 该函数返回当前I/O堆栈单元的指针。 


      MajorFunction: 该IRP的主要功能代码, 它指出所要执行的I/O操作类型。例如: 主功能代码IRP_MJ_READ表示通过Win32 API函数CreateFile发送的请求。主功能代码与驱动程序对像的MajorFunction表中的某个分发函数指针相对应。 


      MinorFunction: 该IRP的副功能代码, 它进一步指出该IRP属于哪个主功能类。例如: IRP_MJ_PNP请求有十几个副功能代码, 包括IRP_MN_START_DEVICE、IRP_MN_REMOVE_DEVICE等。 


      DeviceObject: 指向该堆栈单元对应的设备对象地址, 该域由IoCallDriver函数负责填写。 


      FileObject: 指向与一个I/O请求有关的文件对象地址。  


1.5.4  I/O功能代码 

      IRP_MJ_CREATE: 打开设备  CreateFile 


IRP_MJ_CLEANUP: 在关闭设备时, 取消挂起的I/O请求  CloseHandle       


IRP_MJ_CLOSE: 关闭设备  CloseHandle


 IRP_MJ_READ: 从设备获得数据  ReadFile       


IRP_MJ_WRITE: 向设备发送数据  WriteFile 


       IRP_MJ_DEVICE_CONTROL: 对用户模式或内核模式客户可用的控制操作  DeviceControl 


       IRP_MJ_INTERNAL_DEVICE_CONTROL: 只对内核模式客户程序可用的控制操作  没有对应的Win32 API 


IRP_MJ_QUERY_INFORMATION: 得到文件的长度  GetFileLength       


IRP_MJ_SET_INFORMATION: 设置文件的长度  SetFileLength 


      IRP_MJ_FLUSH_BUFFERS: 写输出缓冲区或丢弃输入缓冲区  FlushFileBuffers  FlushConsoleInputBuffer  PureComm 


      IRP_MJ_SHUTDOWN: 系统关闭  InitialSystemShutdown   


WDF驱动开发(1)- 一个简单的WDF驱动(non-pnp)


    WDF驱动其实是微软公司提供的一套驱动开发的框架。有了这个框架之后,开发驱动会简单一些。WDF本身是从WDM基础上封装而成的。WDF里面封装了很多对象,如WDFDRIVER等。如果要学习使用WDF来开发驱动,个人感觉还是需要WDM的一些基础,不然很多东西挺难理解的。


写了一个简单的WDF驱动(非pnp),基本步骤如下:


创建framework 驱动对象


几乎任何一个WDF驱动一开始就要创建一个framework的驱动对象,这个对象是所有其他对象的parent对象。


        //初始化WDF_DRIVER_CONFIG
WDF_DRIVER_CONFIG_INIT(
   &cfg,
   NULL //不提供adddevice函数);
 
cfg.DriverInitFlags = WdfDriverInitNonPnpDriver;  //非pnp驱动
cfg.DriverPoolTag   = (ULONG)'PEPU';  
cfg.EvtDriverUnload = EvtDriverUnload;  //卸载函数
 
//
//创建一个framework的驱动对象。
status = WdfDriverCreate(DriverObject,RegistryPath,WDF_NO_OBJECT_ATTRIBUTES,&cfg,&drv);
if(!NT_SUCCESS(status))
{
goto DriverEntry_Complete;
}

KdPrint(("Create wdf driver object successfully\n"));

这样,一个framework的驱动对象就创建好了。


创建一个设备(control device)


1. 要创建一个control device,首先得先分配一块内存


//分配一块内存
device_init = WdfControlDeviceInitAllocate(drv,&SDDL_DEVOBJ_SYS_ALL_ADM_RWX_WORLD_RW_RES_R);  
if( device_init == NULL )  
{    
status = STATUS_INSUFFICIENT_RESOURCES;    
goto DriverEntry_Complete;  
}

WdfControlDeviceInitAllocate函数分配了一块内存给结构WDFDEVICE_INIT,这个结构在创建control device的时候会被用到。更多细节看MSDN:http://msdn.microsoft.com/en-us/library/windows/hardware/ff545841(v=vs.85).aspx

2. 然后给这个设备绑定一个设备名字,注意这个设备名字只能被内核模式下的代码所看到,比如其他的内核驱动,用户模式代码是看不到的。


就好象是WDM里面的IoCreateDevice函数的第三个参数。


//设备名字,如: L"\\Device\\MyWDF_Device",只能被其他的内核驱动看到。
RtlInitUnicodeString(&ustring, MYWDF_KDEVICE);  
 
//将这个设备名字存入DEVICE_INIT结构中
status = WdfDeviceInitAssignName(device_init,&ustring);
 
if(!NT_SUCCESS(status))
{
goto DriverEntry_Complete;
}
 
KdPrint(("Device name Unicode string: %wZ (this name can only be used by other kernel mode code, like other drivers)\n", &ustring));

 


3. 给设备绑定2个回调。



//设置2个回调,FileCreate和FileClose

WDF_FILEOBJECT_CONFIG_INIT(&f_cfg,EvtDeviceFileCreate,EvtFileClose,NULL);

 

//存入DEVICE_INIT结构中

WdfDeviceInitSetFileObjectConfig(device_init,&f_cfg,WDF_NO_OBJECT_ATTRIBUTES);

这样当用户模式的代码调用CreateFile和CloseHandle的时候,这2个回调会被调用。


 


4. 初始化设备属性并且创建设备


这里创建一个control device

WDF_OBJECT_ATTRIBUTES_INIT(&object_attribs);
//创建一个设备. (control device)
status = WdfDeviceCreate(&device_init,&object_attribs,&control_device);
if(!NT_SUCCESS(status))
{
KdPrint(("create device failed\n"));
goto DriverEntry_Complete;
}


5. 创建一个符号连接


有了这个符号连接,用户模式代码才能找到这个设备。就好像WDM里面IoCreateSymbolicLink()做的事情。

//创建一个符号连接
RtlInitUnicodeString(&ustring,MYWDF_LINKNAME);
status = WdfDeviceCreateSymbolicLink(control_device,&ustring);
if( !NT_SUCCESS(status) )  
{
KdPrint(("Failed to create Link\n")); 
goto DriverEntry_Complete;  
}
 
KdPrint(("Create symbolic link successfully, %wZ (user mode code should use this name, like in CreateFile())\n", &ustring));


6. 完成设备的创建


WdfControlFinishInitializing(control_device);


到这里,一个设备就创建成功了。这是一个control device,有关control device可以查看http://msdn.microsoft.com/en-us/library/windows/hardware/ff545424(v=vs.85).aspx


 


framework驱动对象的创建和设备对象的创建都发生在DriverEntry()里面。


 


回调函数的实现


接下来就是回调函数的实现,这里就写了几行简单的代码。

static VOID EvtDriverUnload( WDFDRIVER Driver )
{  
KdPrint(("unload driver\n"));
KdPrint(("Doesn't need to clean up the devices, since we only have control device here\n"));
}/* EvtDriverUnload */
 
VOID EvtDeviceFileCreate( __in WDFDEVICE Device, __in WDFREQUEST Request, __in WDFFILEOBJECT FileObject )
{
KdPrint(("EvtDeviceFileCreate"));
 
WdfRequestComplete(Request, STATUS_SUCCESS);
}
 
VOID EvtFileClose( __in  WDFFILEOBJECT FileObject )
{
KdPrint(("EvtFileClose"));
}

只有在FileCreate里面写了一行代码直接将irp请求完成。


这样一个最最简单的WDF驱动就写完了。


完整代码

#include <fltKernel.h>
#include <wdf.h>
#include <wdfdriver.h>
#include <wdfrequest.h>
 
#define MYWDF_KDEVICE L"\\Device\\MyWDF_Device"//设备名称,其他内核模式下的驱动可以使用
#define MYWDF_LINKNAME L"\\DosDevices\\MyWDF_LINK"//符号连接,这样用户模式下的程序可以使用这个驱动设备。
 
 
 
//声明回调
EVT_WDF_DRIVER_UNLOAD EvtDriverUnload;
EVT_WDF_DEVICE_FILE_CREATE EvtDeviceFileCreate;
EVT_WDF_FILE_CLOSE EvtFileClose;
 
 
NTSTATUS DriverEntry( IN PDRIVER_OBJECT DriverObject , IN PUNICODE_STRING RegistryPath )
{  
NTSTATUS status;
WDF_OBJECT_ATTRIBUTES object_attribs;
 
//驱动对象相关
WDF_DRIVER_CONFIG cfg;//驱动的配置
WDFDRIVER drv = NULL;//wdf framework 驱动对象
 
//设备对象相关
PWDFDEVICE_INIT device_init = NULL;
UNICODE_STRING ustring;
WDF_FILEOBJECT_CONFIG f_cfg;
WDFDEVICE control_device;
PDEVICE_OBJECT dev = NULL;
 
KdPrint(("DriverEntry [start]\n"));
 
//初始化WDF_DRIVER_CONFIG
WDF_DRIVER_CONFIG_INIT(
   &cfg,
   NULL //不提供AddDevice函数
);
 
cfg.DriverInitFlags = WdfDriverInitNonPnpDriver;  //指定非pnp驱动
cfg.DriverPoolTag   = (ULONG)'PEPU';  
cfg.EvtDriverUnload = EvtDriverUnload;  //指定卸载函数
 
//创建一个framework驱动对象,在WDF程序里面,WdfDriverCreate是必须要调用的。
//framework驱动对象是其他所有wdf对象的父对象,换句话说framework驱动对象是wdf对象树的顶点,它没有父对象了。
status = WdfDriverCreate(DriverObject,RegistryPath,WDF_NO_OBJECT_ATTRIBUTES,&cfg,&drv);
if(!NT_SUCCESS(status))
{
goto DriverEntry_Complete;
}
 
KdPrint(("Create wdf driver object successfully\n"));
 
 
//创建一个设备
 
//先要分配一块内存WDFDEVICE_INIT,这块内存在创建设备的时候会用到。
device_init = WdfControlDeviceInitAllocate(drv,&SDDL_DEVOBJ_SYS_ALL_ADM_RWX_WORLD_RW_RES_R);  
if( device_init == NULL )  
{    
status = STATUS_INSUFFICIENT_RESOURCES;    
goto DriverEntry_Complete;  
}
 
//创建设备的名字,内核模式下,名字类似: L"\\Device\\MyWDF_Device"
RtlInitUnicodeString(&ustring, MYWDF_KDEVICE);  
 
//将设备名字存入device_init中
status = WdfDeviceInitAssignName(device_init,&ustring);
 
if(!NT_SUCCESS(status))
{
goto DriverEntry_Complete;
}
 
KdPrint(("Device name Unicode string: %wZ (this name can only be used by other kernel mode code, like other drivers)\n", &ustring));
 
//配置FILEOBJECT配置文件,设置FILECREATE,FILECLOSE回调。
WDF_FILEOBJECT_CONFIG_INIT(&f_cfg,EvtDeviceFileCreate,EvtFileClose,NULL);
 
//将FILEOBJECT的设置存入device_init中
WdfDeviceInitSetFileObjectConfig(device_init,&f_cfg,WDF_NO_OBJECT_ATTRIBUTES);
 
//初始化设备属性
WDF_OBJECT_ATTRIBUTES_INIT(&object_attribs);
//根据前面创建的device_init来创建一个设备. (control device)
status = WdfDeviceCreate(&device_init,&object_attribs,&control_device);
if(!NT_SUCCESS(status))
{
KdPrint(("create device failed\n"));
goto DriverEntry_Complete;
}
 
//创建符号连接,这样用户模式下的程序可以使用这个驱动。这个是必须的,不然用户模式下的程序不能访问这个设备。
RtlInitUnicodeString(&ustring,MYWDF_LINKNAME);
status = WdfDeviceCreateSymbolicLink(control_device,&ustring);
if( !NT_SUCCESS(status) )  
{
KdPrint(("Failed to create Link\n")); 
goto DriverEntry_Complete;  
}
 
KdPrint(("Create symbolic link successfully, %wZ (user mode code should use this name, like in CreateFile())\n", &ustring));
 
WdfControlFinishInitializing(control_device);//创建设备完成。
 
/*******************************************
到这里,我们就成功创建了一个control device。
control device 是不支持png和power的,而且我们也不需要手工是删除。
因为framework会帮我们删除,看MSDN
If your driver creates control device objects but does not create framework device objects that support PnP and power management, 
the driver does not have to delete the control device objects. 
In this case, the framework deletes the control device objects after the driver's EvtDriverUnload callback function returns. 
 
更多细节看MSDN,如
http://msdn.microsoft.com/en-us/library/windows/hardware/ff545424(v=vs.85).aspx
*******************************************/
 
 
KdPrint(("Create device object successfully\n"));
 
 
KdPrint(("DriverEntry succeeds [end]\n"));
DriverEntry_Complete:
 
return status;
}
 
 
 
static VOID EvtDriverUnload( WDFDRIVER Driver )
{  
KdPrint(("unload driver\n"));
KdPrint(("Doesn't need to clean up the devices, since we only have control device here\n"));
}/* EvtDriverUnload */
 
VOID EvtDeviceFileCreate( __in WDFDEVICE Device, __in WDFREQUEST Request, __in WDFFILEOBJECT FileObject )
{
KdPrint(("EvtDeviceFileCreate"));
 
WdfRequestComplete(Request, STATUS_SUCCESS);
}
 
VOID EvtFileClose( __in  WDFFILEOBJECT FileObject )
{
KdPrint(("EvtFileClose"));
}

用DDK编译一下就可以了,顺便贴一下sources里面的内容

TARGETNAME=mywdf
TARGETTYPE=DRIVER
DRIVERTYPE=UNKNOWN
 
KMDF_VERSION_MAJOR=1
KMDF_VERSION_MINOR=9
UMDF_MINOR_VERSION=9
 
WINDDK=c:\winddk\7600.16385.0
 
TARGETLIBS= $(DDK_LIB_PATH)\Rtlver.lib \
            $(DDK_LIB_PATH)\Wdmsec.lib
 
#C_DEFINES=$(C_DEFINES)
 
INCLUDES=$(WINDDK)\inc\wdf\kmdf\$(KMDF_VERSION_MAJOR)$(KMDF_VERSION_MINOR);..\
 
SOURCES = \
mywdf.c

这样就得到了一个mywdf.sys,然后安装。一直以来,驱动的安装总是让人那么纠结。一堆的配置inf,好在已经有很多现成的了。网上搜索就可以搜到一堆,这里就不在介绍了。


安装完驱动后,这个驱动有什么用呢?其实啥用都没有,但是可以写几行代码证实一下。如:

#include "stdafx.h"
#include <Windows.h>
 
#define MYDEVICE L"\\\\.\\MyWDF_LINK"
 
int _tmain(int argc, _TCHAR* argv[])
{
HANDLE hDevice = CreateFile(MYDEVICE,GENERIC_READ | GENERIC_WRITE,0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
 
if (hDevice == INVALID_HANDLE_VALUE)
{
wprintf(L"Failed to open device %s, err: %x\n", MYDEVICE, GetLastError());
}
else
{
wprintf(L"Open device %s successfully\n", MYDEVICE);
CloseHandle(hDevice);
 
wprintf(L"Closed handle\n");
}
 
return 0;
}

这是个用户模式代码,运行一下,就会发现




用debugview看一下驱动的输出,看到了驱动里面的log,这就说明用户模式的代码CreateFile和CloseHandle成功的引起驱动调用2个回调。

好文章,来自【福优学苑@音视频+流媒体】
***【在线视频教程】***