windows驱动开发-WDF框架

发布于:2024-05-08 ⋅ 阅读:(27) ⋅ 点赞:(0)

WDF框架属于对WDM框架的封装,不过看起来,WDF是和WDM是完全不同的两个框架。对于驱动开发来说,WDM框架学习的意义在于理解内核是怎么运作的,毕竟WDM跨越了20年,仍然能够和好的适应windows现在的发展,说明这个体系是非常稳定和可扩展的;但是WDM框架确实非常难以开发程序,在WDM框架问世后不久,许多优秀的程序员都曾经自行封装过WDM,使它更加容易开发。

在这一点上,WDF框架做得很出色,熟悉和了解WDF框架之后可以发非常轻松的写出质量比较好的驱动程序,而且不会花费太多时间,这一点非常重要,驱动的代码往往只有几百行到几千行,大量的时间被用来调试和系统进行对接部分,驱动是非常难以调试的。

驱动的设计上应该尽可能求简约、高效、稳定,简单是因为在一个系统中,越简单的步骤越不容易出错;高效是因为驱动本身需要考虑到对系统的影响,它申请和涉及的资源都对系统非常重要,这种情况下,一个简单的延时或者自旋操作都很容易造成严重的后果;稳定则是因为驱动需要在内核中运行,一旦出错,往往带来的是蓝屏这样严重的后果。

和大部分人设想的不一样WDF对WDM的封装并不是以C++的形式,仍旧以C语言的方式开发程序,类似于C-Class的形式开发。这很容易理解,使用C++的方式封装WDM是为了让驱动程序开发变得简单,使用C编译的方式是为了保障兼容性,

WDF 驱动程序包括 Kernel-Mode Driver Framework (KMDF) 和 User-Mode Driver Framework (UMDF) ,对于UMDF来说,UMDF1.0和UMDF2.0完全是两个东西,这可能是windows历史发展原因造成,故我们仅仅简单的介绍UMDF1.0 之后就不再赘述了,毕竟所有1.0的驱动都可以用2.0的版本开发,1.0的存在仅仅是为了兼容。

UMDF程序是以WUDFHost.exe为宿主运行的,在大部分情况下,它们起到一个宿主程序的功能,就像之前说的那样,代码倒不是非运行于内核层不可,运行于应用层,在出错的情况下总好过蓝屏,至于效率,驱动层增加转发和处理的效率其实也是在微秒级别,故牺牲一点点效率来让程序和系统更加稳定是一件非常好的事情。

WDF框架式一个半开源框架,它的源代码可以在Git上获取,当然,不要尝试编译去替换系统的WDF框架,开元这个框架的目的是为了让大家可以了解内部的实现机制,而不是让我们去修改的,下载地址如下: 

WDF框架链接

UMDF 1.0

1.0版本的UMDF最大的特征就是它完全是基于com组件的风格,毫无疑问的,com组件在一定程度上windows的基石之一,许多新技术都在它的基础上发展起来,但问题在于这一项很难了解清晰的技术,对于新书宛如天书一样。

这实际上违背了内核开发的核心: 内核开发需要非常简单,尽可能高效,尽可能可靠,com组件技术不是好不好的问题,而是适不适合的问题,这也是最终技术线路还是回到C语言的原因,接下来所有的讨论都和UMDF 1.0的技术无关。

UMDF 2.0

从 UMDF 版本 2 开始,可以使用 C 编程语言编写 UMDF 驱动程序,该语言调用许多可用于 KMDF 驱动程序的方法。 UMDF 2.0 和 KMDF 之间共享的所有接口具有相同的名称、参数和结构定义。 如果驱动程序仅使用共享功能,或者围绕仅在一个框架中支持的调用使用条件宏,则可以编写可以使用 UMDF 或 KMDF 编译的单个驱动程序。

何时使用UMDF

一般如果驱动不涉及下面几种情况,那么可以使用UMDF来开发:

  • 驱动需要DMA直接访问内存;
  • 驱动总线枚举;
  • 驱动需要支持功能性电源状态;
  • 驱动访问 WDM 对象和 IRP ;
  • 自定义I/O缓冲区;
  • IOCTL的内部设备控制请求;
  • 驱动需要使用WMI 
  • 驱动需要删除 I/O请求的锁定选项 
为什么使用UMDF

编写 UMDF 驱动程序主要由下面几种好处:

UMDF 驱动程序有助于提高操作系统稳定性,因为它们只能访问运行它们的进程的地址空间;

由于 UMDF 驱动程序在 LocalService 帐户下运行,因此它们对用户数据或系统文件的访问权限有限;

用户模式驱动程序在比内核模式驱动程序简单得多的环境中运行。 例如,内核模式驱动程序必须考虑 IRQL、页面错误和线程上下文。 但是,在用户模式下,这些问题不存在。 用户模式驱动程序始终在与请求进程不同的线程中运行,并且始终可能会发生页面错误;

UMDF 版本 2 在大多数区域中提供与 KMDF 的功能奇偶一性;

UMDF 版本 2 有助于在 KMDF 和 UMDF 之间进行转换;

可以使用用户模式调试器或从 UMDF 版本 2 开始的内核模式调试器来调试 UMDF 驱动程序;

可以将 Wdfkd.dll 调试器扩展命令与 KMDF 一起使用,并从 UMDF 版本 2 开始;

整个 WDF 模型的基本目标是提供智能默认值,以便你可以专注于设备硬件,并避免编写代码来执行大多数驱动程序通用的任务。

为了实现此目标,框架设计为在“选择加入”的基础上与驱动程序配合使用。 编写 UMDF 驱动程序时,仅为影响设备的事件提供回调例程。 例如,某些设备在打开后和关闭设备之前需要立即干预。 此类设备的驱动程序可以实现框架在这些时间调用的回调函数。

驱动程序包含的代码仅处理其设备需要特定于设备的支持的事件。 所有其他事件都可以由框架默认值处理。

此外,驱动程序可以配置其 I/O 请求队列,以便框架在设备处于低功耗状态时停止调度请求,并在设备返回到运行状态后继续调度。 同样,如果 I/O 请求在设备处于低功耗状态时到达,框架可以自动打开设备。

UMDF运行原理

 UMDF 驱动程序作为管理设备的驱动程序堆栈的一部分运行。 文件系统驱动程序、显示驱动程序、打印驱动程序不能使用 UMDF 驱动程序。

UMDF 驱动程序与系统提供的以下组件交互:

驱动程序主机进程(WUDFHost.exe): 驱动程序主机进程加载供应商提供的 UMDF 驱动程序和框架 DLL,为用户模式驱动程序提供执行环境,并在用户模式堆栈中的驱动程序之间路由消息。 

驱动程序管理器:驱动程序管理器是一项 Windows 服务,用于管理 Wudfhost 驱动程序主机进程的所有实例。 驱动程序管理器启动并跟踪有关每个驱动程序主机进程的信息。 每个主机都是驱动程序管理器的子进程。 每个系统只有一个驱动程序管理器。 驱动程序管理器在安装第一个 UMDF 设备期间启动,之后在系统上运行。

Reflector(反射器): 反射器是内核模式驱动程序,允许应用程序和驱动程序主机进程 (和用户模式设备堆栈) 进行通信。 反射器为每个设备实例创建一个单独的设备对象,并处理与每个设备实例关联的即插即用 (PnP) 和电源 I/O 请求。 应用程序与驱动程序主机进程之间的所有通信都通过反射器进行。

给定设备的所有函数和筛选器驱动程序都必须在同一驱动程序主机进程中运行,但多个主机进程可以同时运行。

下图显示了驱动程序主机进程、驱动程序管理器和反射器如何跨用户模式/内核模式边界进行通信:

UMDF体系

在运行原理部分我们了解到,UMDF是微软将内核驱动分为两部分,一部分放在内核层,一部分放在应用层,中间使用反射器来进行传递。

与内核模式堆栈类似,用户模式堆栈的构造和析构由即插即用管理器 (PnP) 驱动。生成内核模式堆栈后,反射器会通知驱动程序管理器开始构建用户模式堆栈,驱动程序管理器启动驱动程序主机进程,并为启动的进程提供足够的信息来生成用户模式堆栈。 通过这种方式,可将用户模式堆栈视为内核模式堆栈的扩展。

驱动程序主机进程为用户模式驱动程序提供执行环境,并在用户模式堆栈中的驱动程序之间来回床底消息,它使用基于消息的进程间通信机制来与驱动程序管理器和主机进程进行通信。

 

若要向 UMDF 驱动程序发送 I/O 请求,应用程序会调用 Win32 文件 I/O 函数,例如 CreateFile、 ReadFileEx、 CancelIoEx 或 DeviceIoControl。 当反射器收到来自客户端应用程序的请求时,它会将请求发送到相应的驱动程序主机进程。 然后,驱动程序主机进程将请求路由到正确的用户模式设备堆栈的顶部。

请求由用户模式堆栈中的某个驱动程序完成,或由其中一个驱动程序转发回反射器。 当反射器收到来自用户模式驱动程序堆栈的请求时,它会在内核模式堆栈中向下发送请求以完成。

KMDF

相反,KMDF框架本身其实没有特别的,不过,有种说法是KMDF框架本身集合了UMDF框架,许多函数内部都会使用特权指令来判断自己是在KMDF还是在UMDF中被调用,所以我们无需显式的指出函数到底是在UMDF还是在KMDF中被调用的。

我们会从下面几个方向来介绍WDF:

WDF框架

WDF对象

PNP和电源管理

电源策略

I/O请求

访问硬件资源

中断和DMA