浅述Windows驱动程序的开发过程

(整期优先)网络出版时间:2022-09-21
/ 3

浅述Windows驱动程序的开发过程

黎钦银

壹倍科技(东莞)有限公司     523000

摘要:本文以创建默认的虚拟驱动程序为例子,从实用性的角度阐述如何搭建Windows驱动的开发环境和调试驱动程序。涉及到Visual Studio的使用和WDF驱动模型,并以WDK驱动程序框架为基础,厘清驱动程序在系统中的实现方式和工作过程。

关键词Windows,WDF驱动模型,WDK驱动框架

The development process of Windows drivers

Abstarct:This article takes the creation of a default virtual driver as an example to illustrate how to build a Windows-driven development environment and how to debug drivers . It involves the use of Visual Studio and the WDF driver model, and is based on the WDK driver framework to clarify how the driver is implemented and how it works in the windows system.

Key words:Windows,WDF driver model,WDK driver framework

1、开发环境介绍及搭建

驱动开发需要两台主机,分别是开发主机和测试主机。开发主机用于编写驱动程序,测试主机用于运行调试驱动。由于驱动运行调试的时候可能会导致主机进入“假死”的状态,所以建议使用虚拟机作为测试主机使用。

1.1开发主机环境的搭建

开发的系统环境使用的是Windows 10。驱动开发环境的安装步骤如下:

步骤1:安装Visual Studio 2019 Community;

步骤2:安装Windows SDK(Windows Software Development Kit);

步骤3:安装Windows WDK[1](Windows Driver Kit);

这里需要注意的是SDK的版本与WDK的版本需要对应上,如果版本对应不上的话,驱动编译的时候很可能会出错。例如安装适用于Windows 10版本2004的SDK和WDK;安装后的版本号是10.0.19041.685,该版本号可在控制面板“程序和功能”中查看,如图1-1:

图1-1

1.2 测试主机环境的搭建

开发主机成功安装WDK和SDK之后,在开发主机中打开WDK的安装目录找到WDK测试程序库,默认安装的目录为:C:\Program Files (x86)\Windows Kits\10\Testing\Runtimes\,将该目录的程序拷贝到测试主机并安装与系统相应的版本。测试主机安装好以上目录的运行库,才能在测试主机调试驱动程序。

2、驱动程序的调试工具

驱动程序调试的工具有很多种,例如,可以使用TraceView程序对驱动进行跟踪调试,默认的安装目录为:C:\Program Files (x86)\Windows Kits\10\Tools\x64,建议将整个Tools目录拷贝到测试主机中使用。TraceView旨在将许多传统的单函数命令行跟踪工具替换为一个工具,该工具可在图形界面下跟踪驱动程序运行时的调试信息[2]。你可以在驱动程序中使用函数TraceEvents函数打印信息,然后在测试主机中运行TraceView程序跟踪驱动运行时打印的信息。值得注意的是,使用TraceView程序进行调试时,程序需要按Debug编译部署,并把整个程序代码也拷贝到测试主机,这样TraceView才能打开Debug文件夹中的PDB文件,然后创建跟踪打印信息。

3、WDF驱动模型

驱动可以分为不同的类型,如NT驱动、WDM驱动、WDF驱动等。简单来说,NT驱动是最简单的驱动模型,不支持硬件特性;WDM驱动是在NT驱动基础上引入的一套驱动模型,支持即插即用、电源事件等特性;WDF驱动是对WDM驱动的封装与升级,屏蔽了部分细节,简化了大量接口[3]。值得注意的是,WDF驱动模型并不是替代WDM驱动的,而是一个可以帮助开发人员更轻松编写符合WDM要求驱动程序的框架,所以用WDF驱动模型开发的驱动是向前兼容的,编写的驱动程序只要稍作修改甚至不用修改就能在比较旧的Windows系统上运行。Windows 7之后的操作系统,建议都使用WDF驱动模型,WDF驱动模型的构造和语法更加清晰,会节省非常大的时间和精力。

WDF驱动模型又分为两种框架:内核驱动框架(Kernel-Mode Driver Framework,KMDF)和用户态驱动程序框架(User-Mode Driver Frame Work,UMDF)

[4]。这两种框架不同之处在于,KMDF驱动是运行在内核态下的,而UMDF是运行在用户态下的。WDF的精妙之处就在于,统一了用户态和内核态的开发框架,两种程序框架开发起来是非常相似的。但内核态和用户态下能调用的函数是有区别的,例如,在UMDF驱动开发中,可以使用CreateFile函数来创建设备文件的句柄,而要使用ZwCreateFile函数来创建。

4、WDK程序框架

创建一个空的驱动程序后,主要有四个文件,inf文件、Device.c、Driver.c、Queue.c和相应的头文件;这几个文件的函数定义了WDK驱动程序的基本框架。

4.1 INF文件

INF是Device Information的缩写,由于在应用上,INF文件总是与驱动安装关联在一起,所以习惯上INF文件又被称为“安装文件”。它包含了设备安装指令,系统通过这些指令来理解设备,并将驱动程序与设备联系在一起。INF文件被安装后,系统会为它生成一个同名的PNF文件,PNF文件称作Precompiled INF或预编译安装文件[5]

举个例子,主机有多种硬件设备,Windows就是通过INF文件中的ClassGuid值来区分不同的硬件设备的。例如,串口设备的ClassGuid值就是{4D36E978-E325-11CE-BFC1-08002BE10318},当系统检测到该类Guid,就知道该驱动程序要驱动的是串口设备。

4.2 驱动程序初始化执行流程图

Device.c、Driver.c、Queue.c这几个文件包含了WDK程序框架的执行代码。驱动代码的执行流程如图4-1:

驱动流程图1

图4-1

4.3 Driver.c文件

Driver.c文件主要定义了以下几个函数:

DRIVER_INITIALIZE DriverEntry;

EVT_WDF_DRIVER_DEVICE_ADD EvtDeviceAdd;

EVT_WDF_OBJECT_CONTEXT_CLEANUP EvtDriverContextCleanup;

其中,DRIVER_INITIALIZE、EVT_WDF_DRIVER_DEVICE_ADD、EVT_WDF_OBJECT_CONTEXT_CLEANUP是WDF驱动模型定义的回调函数类型。

DriverEntry是驱动程序的入口函数,驱动程序就是从该函数开始执行的,相当于main函数。EvtDriverContextCleanup函数是驱动的清理函数,清理函数的作用是,当用户卸载设备的时候,用于清理驱动程序执行过程中申请的资源;或者当驱动程序执行有错误的时候,该函数能使驱动程序顺利地卸载而不影响系统运行的稳定性。换言之,DriverEntry是驱动程序开始执行的第一个函数,EvtDriverContextCleanup是驱动结束时最后执行的函数。

驱动程序操作的是设备,不管是虚拟的设备还是真实的硬件设备,我们都需要有一个设备对象来指代,所以在入口函数DriverEntry中会调用EvtDeviceAdd函数,用于创建驱动的设备对象。

4.4 Device.c文件

EvtDeviceAdd函数先调用WdfDeviceCreate函数创建设备,一般这个时候会绑定设备的上下文。设备的上下文就是可以自定义一个结构体,该结构体用于保存设备的资源、信息。该函数还会调用WdfDeviceCreateDeviceInterface函数,创建设备接口,原型如下:

NTSTATUS WdfDeviceCreateDeviceInterface(

  [in]           WDFDEVICE        Device,

  [in]           const GUID         *InterfaceClassGUID,

  [in, optional]   PCUNICODE_STRING ReferenceString );

值得注意的是InterfaceClassGuid,区别于上述的ClassGuid,这里是设备的接口Guid,对于常见的设备,Windows默认能识别的设备的接口Guid也是固定的,例如串口的接口Guid为{86e0d1e0-8089-11d0-9ce4-08003e301f73},该Guid在winioctl.h头文件中定义。

顾名思义,接口是留给用户程序调用该驱动的方法。事实上,用户程序如果希望调用某设备,就要通过调用该设备的驱动程序接口来驱动设备。上位机程序可以通过某些方法,例如查找注册表中的接口Guid来找到设备的路径,从而调用设备。

4.5 Queue.c文件

设备接口也创建成功之后,驱动程序就可以创建队列了。Queue.c文件调用的是DriverQueueInitialize函数,该函数在Queue.h中定义。以串口驱动为例,对于串口而言,用户程序可以打开串口、关闭串口、设定串口的波特率、奇偶校验等操作,这些操作都是上位机程序对驱动程序的一个请求。驱动程序会把这些请求一一压入驱动的队列中,按队列的入列顺序一一处理,直到队列中的请求都完成为止,之后,队列就进入等待请求的状态,如图4-2:

驱动队列处理流程1

图4-2

5、结束语

以上只是对驱动开发入门的介绍,驱动还有很多的内容,如过滤驱动、自定义用户请求、电源管理、数字签名、驱动安装等等。开发驱动是一件比较困难的事情,开发者要有牢固的C语言基础和较好的英语阅读能力,需要查找的资料也比较多。如果遇到困难,建议还是直接查找微软官方的在线文档,官方文档的资料是最准确的,参考以下链接:https://docs.microsoft.com/zh-cn/windows-hardware/drivers/。

参考文献:

[1]微软官网.下载Windows驱动程序工具包 (WDK).https://docs.microsoft.com/zh-cn/windows-hardware/drivers/download-the-wdk.

[2]微软官网.TraceView概述.https://docs.microsoft.com/zh-cn/windows-hardware/drivers/devtest/traceview-overview.

[3]谭文,陈铭霖.Windows内核编程[M].北京:电子工业出版社,2020:1-2.

[4]塔里克·索拉米.Windows编程调试技术内幕[M].曹军,译.北京:人民邮电出版社,2021:14.

[5]张佩,马勇,董鉴源.竹林蹊径:深入浅出Windows驱动开发[M].电子工业出版社,2011:910.