亲手解剖WDF驱动
亲手解剖WDF驱动
创建一个WDF驱动(non-pnp)
WDF驱动其实是微软公司提供的一套驱动开发的框架。
有了这个框架之后,开发驱动会简单一些。WDF本身是从WDM基础上封装而成的。WDF里面封装了很多对象,如WDFDRIVER等。如果要学习使用WDF来开发驱动,个人感觉还是需要WDM的一些基础,不然很多东西挺难理解的。
写了一个简单的WDF驱动(非pnp),基本步骤如下:
创建framework 驱动对象
总结:
1.创建WDF驱动对象:WDFDRIVER
2.创建WDF设备对象:WDFDEVICE
原始驱动对象:Nt/wdm: DRIVER_OBJECT
WDF驱动对象:WDFDRIVER
几乎任何一个WDF驱动一开始就要创建一个framework的驱动对象,这个对象是所有其他对象的parent对象。
//初始化WDF_DRIVER_CONFIG
WDF_DRIVER_CONFIG_INIT(
&cfg,
NULL //不提供adddevice函数//不支持pnp );
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"\\??\\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")); /////创建一个设备...begin /////........................ //先要分配一块内存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回调。 /// IRJ_MJ_CREATE, IRJ_MJ_CLOSE: 对应的派遣函数 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; } //创建符号连接,这样用户模式下的程序可以使用这个驱动。这个是必须的,不然用户模式下的程序不能访问这个设备。 /// IoCreateSysbolicLink(sysbolic_link_name, dev_name); 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);//创建设备完成。 /////创建一个设备...begin /******************************************* 到这里,我们就成功创建了一个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")); /// IoCompleteRequest(pIrp);完成irp WdfRequestComplete(Request, STATUS_SUCCESS); } VOID EvtFileClose( __in WDFFILEOBJECT FileObject ) { KdPrint(("EvtFileClose")); }
编译错误
使用VS2015+WDK10 在学习windows WDF驱动时候,使用下面链接文章提供的代码编译后,得到错误:
LNK2001无法解析的外部符号 SDDL_DEVOBJ_SYS_ALL_ADM_RWX_WORLD_RW_RES_R
http://blog.csdn.net/zj510/article/details/16983863
https://blog.csdn.net/wuoo98/article/details/54970782
解决方法:链接属性设置中增加
$(DDK_LIB_PATH)\wdmsec.lib
应用层程序
#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个回调。
安装调试
设备管理器-->添加过时硬件-->安装我手动从列表选择的硬件-->从磁盘安装
知识点总结
//声明回调
EVT_WDF_DRIVER_UNLOAD EvtDriverUnload;
EVT_WDF_DEVICE_FILE_CREATE EvtDeviceFileCreate;
EVT_WDF_FILE_CLOSE EvtFileClose;
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;
//初始化WDF_DRIVER_CONFIG
WDF_DRIVER_CONFIG_INIT(...)
WdfDriverCreate(...)
WdfControlDeviceInitAllocate(...)
RtlInitUnicodeString(...)
WdfDeviceInitAssignName(...)
WDF_FILEOBJECT_CONFIG_INIT(...)
WdfDeviceInitSetFileObjectConfig(...)
WDF_OBJECT_ATTRIBUTES_INIT(...)
WdfDeviceCreate(...)
WdfDeviceCreateSymbolicLink(...)
WdfControlFinishInitializing(...)
WdfRequestComplete(...)
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.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请求进入队列时,wdf框架将自动调用驱动程序中对应的callback。
1.2.5 WDFINTERRUPT对象
表示设备中断。驱动程序可以通过WDFINTERRUPT对象的中断使能enable和禁止disable事件处理callbacks使能或禁止设备中断;通过ISR和DPC for ISR例程处理设备中断。
1.3 WDF的对象模型是层次化的模型。
WDFDRIVER对象是根对象,其他对象都是它的子对象。对于大多数对象,驱动程序在创建它们的时候可以指定父对象,如果没有指定,则框架默认其父对象为WDF DRIVER对象。
WDF大大简化了WDM中的PNP和电源管理的开发。
WDM---> IRP_MJ_PNP,IRP_MN_START_DEVICE/STOP_DEVICE/REMOVE_DEVICE/...
MJ: major,, MN: minor
WDF框架为设备停止、设备删除、电源状态切换等PNP和电源管理事件提供了适合的缺省行为,驱动程序本身不再纠缠于复杂的PNP和电源管理事件处理。
1.4 WDF请求队列
一个设备可以有多个请求队列,每个请求队列可以有一种模式。
1.4.1 WdfIoQueueDispatchSerial模式
请求队列将请求串行化后再处理
1.4.2 WdfIoQueueDispatchParallel模式
自动在每个请求到来时调用相应的回调函数;
1.4.3 WdfIoQueueDispatchManual模式
允许驱动程序手工分发请求,类似于WDM的工作方式。
IoSetCancelRoutine(...), interrupt, irql,
在WDM驱动程序中,I/O请求的取消是一个复杂难以理解的过程,开发人员必须有对内核深刻的理解才能正确处理I/O请求的取消。
WDF框架支持内建的I/O请求取消处理,使得驱动程序处理取消I/O请求的工作大大简化。
1.5 I/O请求包(IRP, WDFRequest)
1.5.1 IRP简介
IRP是I/O系统用来存储处理I/O请求所需信息的地方, I/O管理器在IRP中保存一个指向调用者文件对象(FILE_OBJECT,设备:内联一个文件对象)的指针。
:所有设备都被当做 文件 来统一处理
CreateFile,CloseHandle,ReadFile, WriteFile,DeviceIoControl,CancelIo
ReadFile(handle,buf, len, &bytes, flags,....),
从编程的角度看, 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模型驱动程序开发
WDF驱动程序开发
1. 引言
设备驱动程序是硬件设备连接到计算机系统的软件接口,任何设备都必须有相应的驱动程序才能在计算机系统上正常工作。设备驱动程序的优劣直接关系到整个系统的性能和稳定性,因此,设计和开发稳定高效的驱动程序具有重要意义。
WDF(Windows Driver Foundation)是微软提出的下一代全新的驱动程序模型,它是在WDM(windows Driver Model)的基础上发展而来的,支持面向对象、事件驱动的驱动程序开发,提供了比WDM更高层次抽象的高度灵活、可扩展、可诊断的驱动程序框架。
WDF框架管理了大多数与操作系统相关的交互,实现了公共的驱动程序功能(如电源管理、PnP支持),隔离了设备驱动程序与操作系统内核,降低了驱动程序对内核的影响。
WDF提供了两个框架:KMDF(内核模式驱动程序框架)和UMDF(用户模式驱动程序框架)。本文只介绍KMDF的设计与实现。
2. WDF对象模型
KMDF框架支持面向对象、事件驱动的驱动程序模型。它定义了一系列的对象用来表示设备、驱动、中断等,每个对象有对应的属性、方法和事件。驱动程序利用这些方法创建对象、设置属性和响应事件。
框架定义的主要对象有:
WDFDRIVER对象,对应于WDM中的DRIVER_OBJECT。描述驱动在内存中的实例,包括加载的位置、有关的属性和所管理的设备。
WDFDEVICE对象,对应于WDM中的DEVICE_OBJECT。描述由驱动程序管理的单个设备实例,设备可以是命名的也可以是未命名的。用户模式程序可以通过设备接口或设备名称访问设备。WDFDEVICE对象具有丰富的属性,如pnp和电源管理相关的事件处理回调函数(callbacks)。
WDFREQUEST对象,对应于WDM中的IRP,表示一个I/O请求。
WDFQUEUE对象:每个WDFQUEUE对象和一个WDFDEVICE对象关联,描述一个特殊的I/O请求队列。它具有一系列的事件处理回调函数,当I/O请求进入队列时,框架将自动调用驱动程序中对应的callback。
WDFINTERRUPT对象:表示设备中断。驱动程序可以通过WDFINTERRUPT对象的中断使能和禁止事件处理callbacks使能或禁止设备中断;通过ISR和DPCforISR例程处理设备中断。
WDF的对象模型是层次化的模型。WDFDRIVER对象是根对象,其他对象都是它的子对象。对于大多数对象,驱动程序在创建他们的时候可以指定父对象,如果没有指定,则框架默认其父对象为WDFDRIVER对象。
WDF大大简化了WDM中的pnp和电源管理的开发。WDF框架为设备停止、设备删除、电源状态切换等pnp和电源管理事件提供了适合的缺省行为,驱动程序本身不再纠缠于复杂的pnp和电源管理事件处理。
此外,WDF还集成了请求队列的支持,一个设备可以有多个请求队列,每个请求队列可以有一种模式。最简单的是 WdfIoQueueDispatchSerial模式,在这种模式下,请求队列将请求串行化后再处理;而WdfIoQueueDispatchParallel模式则自动在每个请求到来时调用相应的回调函数;最后WdfIoQueueDispatchManual模式允许驱动程序手工分发请求,类似于WDM的工作方式。
在WDM驱动程序中,I/O请求的取消是一个复杂难以理解的过程,开发人员必须有对内核深刻的理解才能正确处理I/O请求的取消。WDF框架支持内建的I/O请求取消处理,使得驱动程序处理取消I/O请求的工作大大简化。
3. WDF设备驱动程序的结构
与WDM驱动程序一样,WDF驱动程序得标准入口函数是DriverEntry。与WDM不同,WDF的DriverEntry只负责创建和初始化WDFDRIVER对象,告诉WDF框架处理增加新设备连接的回调函数。
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObj, PUNICODE_STRING RegistryPath) { NTSTATUS code; WDF_DRIVER_CONFIG config; WDFDRIVER hDriver; // 初始化驱动配置结构,指定设备添加事件callback WDF_DRIVER_CONFIG_INIT_NO_CONSTRAINTS(&config, MyEvtDeviceAdd); // 创建WDFDRIVER对象 code = WdfDriverCreate(DriverObj, RegistryPath, WDF_NO_OBJECT_ATTRIBUTES, &config, // 指向config结构的指针 NULL); return(code); }
每当有新设备连接到系统时,WDF框架自动调用EvtDeviceAdd设备添加callback。该回调函数初始化pnp和电源管理相关结构,设置相应的事件处理callbacks,然后创建WDFDEVICE对象和符号连接,初始化请求队列、中断处理等相关结构,设置相应的回调函数。
WDFSTATUS MyEvtDeviceAdd(WDFDRIVER Driver, PWDFDEVICE_INIT DeviceInit) { WDFSTATUS status = STATUS_SUCCESS; WDF_PNPPOWER_EVENT_CALLBACKS pnpPowerCallbacks; WDF_OBJECT_ATTRIBUTES objAttributes; WDFDEVICE device; PMY_DEVICE_CONTEXT devContext; WDF_IO_QUEUE_CONFIG ioCallbacks; WDF_INTERRUPT_CONFIG interruptConfig; ///1. 设备对象 ///2. IO请求队列 ///3. 中断对象 // 初始化pnpPowerCallbacks ,设置与PnP和电源管理相关的事件回调函数 WDF_PNPPOWER_EVENT_CALLBACKS_INIT(&pnpPowerCallbacks); pnpPowerCallbacks.EvtDevicePrepareHardware = MyEvtPrepareHardware; pnpPowerCallbacks.EvtDeviceReleaseHardware = MyEvtReleaseHardware; pnpPowerCallbacks.EvtDeviceD0Entry= MyEvtDeviceD0Entry; pnpPowerCallbacks.EvtDeviceD0Exit = MyEvtDeviceD0Exit; WdfDeviceInitSetPnpPowerEventCallbacks(DeviceInit, pnpPowerCallbacks); // 创建设备对象,初始化相关的属性 WDF_OBJECT_ATTRIBUTES_INIT(&objAttributes); WDF_OBJECT_ATTRIBUTES_SET_CONTEXT_TYPE(&objAttributes, MY_DEVICE_CONTEXT); // 命名设备 status = WdfDeviceInitUpdateName(DeviceInit, L"\\device\\WDFDEMO"); if (!NT_SUCCESS(status)) return(status); status = WdfDeviceCreate(&DeviceInit, &objAttributes, &device); if ( !NT_SUCCESS(status)) return(status); devContext = MyGetContextFromDevice(device); devContext->WdfDevice = device; // 创建符号连接 status = WdfDeviceCreateSymbolicLink(device, L"\\DosDevices\\WDFDEMO"); if (!NT_SUCCESS(status)) return(status); // 创建和初始化请求队列 WDF_IO_QUEUE_CONFIG_INIT(&ioCallbacks, WdfIoQueueDispatchSerial, WDF_NO_EVENT_CALLBACK, WDF_NO_EVENT_CALLBACK); ioCallbacks.EvtIoDeviceControl = MyEvtDeviceControlIoctl; status = WdfDeviceCreateDefaultQueue(device,&ioCallbacks, WDF_NO_OBJECT_ATTRIBUTES,NULL); if (!NT_SUCCESS(status)) return(status); // 创建和初始化中断对象,MyIsr和MyDpc分别是isr中断服务例程和DPC例程 WDF_INTERRUPT_CONFIG_INIT(&interruptConfig, FALSE, MyIsr, MyDpc); interruptConfig.EvtInterruptEnable = MyEvtInterruptEnable;//中断使能 interruptConfig.EvtInterruptDisable = MyEvtInterruptDisable;//中断禁止 status = WdfInterruptCreate(device, &interruptConfig,&objAttributes, &devContext->WdfInterrupt); return(status); }
WDF驱动程序下一步的工作就是编写各事件处理回调函数,当相应事件发生时,WDF框架会自动调用指定的回调函数进行处理。
其中EvtDevicePrepareHardware回调函数在分配资源的时候被调用,框架将分配给设备的资源传递给回调函数,回调函数保存需要的资源,将共享内存映射到内核虚拟地址空间。与此对应的是EvtDeviceReleaseHardware回调函数,每当设备释放所占用的资源时,框架都将调用它。
EvtDeviceD0Entry 和 EvtDeviceD0Exit事件callbacks则分别在设备即将进入和离开D0电源状态时调用。EvtIoDeviceControl、EvtIoRead、EvtIoWrite等回调函数分别用来处理DeviceControl、Read、Write I/O请求。
当WDF框架获得一个I/O请求时,它首先确定该请求应该放入哪个请求队列。如果驱动程序没有提供指定的队列,WDF框架默认将请求放入缺省请求队列会自动调用对应的回调函数。然后,框架寻找处理该请求的回调函数,如果驱动程序提供了相应的callback,则调用它处理请求。对于没有指定回调函数的I/O请求,WDF调用EvtIoStart回调函数处理。如果EvtIoStart callback也不存在,框架将返回STATUS_NOT_SUPPORTED。
设备中断的管理由EvtInterruptEnable callback、EvtInterruptDisable callback、中断服务例程(ISR)和DpcForISr例程实现。WDF框架在调用EvtDeviceD0Entry callback和注册ISR后,通过调用EvtInterruptEnable回调函数使能设备中断;而EvtInterruptDisable回调函数则在设备离开D0状态,EvtDeviceD0Exit callback调用前获得调用,完成禁止设备中断的工作。此外,中断服务例程和DpcForIsr例程具体完成中断服务的功能,与WDM驱动程序相似。