编写硬件设备驱动程序一直是一种具有很强挑战性的复杂工作,即便是编写过具有相当难度Win32程序的开发人员,在编写设备驱动程序时也必须去应对种种不适,需要"洗脑"。编写设备驱动程序很象在执行一项艰巨的任务:没有窗口、没有消息需要处理,很难对源代码进行调试设置,几乎所有支持库都无法调用;更糟的是,由于设备驱动程序属于操作系统的信任部分,于是很容易伤害系统,对此开发人员却往往缺乏一种保护手段去防止进程和Windows系统被损。
WDM(Windows driver model)是微软为开发人员提供的一种编写运行在Windows平台下新硬件驱动程序的有效方法,此即所谓的Windows驱动程序模型。WDM提供了设备类(device classes),由此形成的源代码可以运行在各类Windows平台:Win XP/2000/Me/98。需要指出的是,职业开发人员往往使用的是更有深度的DDK,它除了可以开发WDM类型的驱动程序外,还可以开发非WDM类型Windows驱动程序。
WDM是一种便捷式的编程技术,它使开发人员写出的驱动程序可以跨平台运行,支持编写微软所支持的WDM 总线驱动程序,在WDM中"总线"(Bus)的概念是一种附加在其它设备(包括物理设备、逻辑设备、虚拟设备)上的设备协议,例如Windows XP内置的WDM技术支持的总线类型包括:PCI(Peripheral Component Interconnect)、动态式即插即用串行I/O总线(如USB和IEEE 1394)、SCSI(Small Computer System Interface)、NDIS(Network Driver Interface Specification)以及远程式NDIS (RNDIS)。
新版WDM中的RNDIS属于新内容:如简化了对网络设备硬件的开发,减弱了网络设备对驱动程序的依赖性,使最终用户对网络设备的设置安装更简便。WDM新版中对设备的支持主要包括:USB设备类,比如其中的类HID(Human Interface Device);/扫描仪;通过IEEE 1394标准的视频捕捉设备;音频;控制调制解调器的WinModem。在WDM 类代码中提供的port/miniport 驱动范式,支持第三方厂商为其特殊设备编写"迷你型"驱动minidriver。Windows DDK其实是建立在WDM基础之上,它提供了:Driver Verifier 及 Windows Hardware Compatibility Tests测试工具;被WHQL(Windows Hardware Quality Labs)所识别的第三方驱动程序等集成环境。
WDM驱动程序的三种类型
WDM支持的驱动程序具有分层结构,换言之,对于一种设备而言,它可以具有三种类型的drivers:总线driver,或者函数driver,或者过滤式驱动程序(它可以假定或修正设备的行为值)。为一台设备服务的这些驱动程序链就是所谓驱动程序栈。一个驱动程序栈分阶段地处理用户的请求,这些驱动程序一个个相互叠加在一起,低层的总线驱动程序可用于处理与硬件的所有基本联系,而中间的类驱动程序对整个一类驱动程序提供共同的设施。
微软提供了针对Windows的总线驱动程序,并为第三方设备开发商提供有关服务,如枚举设备、对即插即用和I/O所需电源的管理,并提供了独立于设备管理方式。设备开发商更多地是提供函数式驱动,其基本内容包括:对设备的操作界面,对设备的读写句柄,对设备电源的管理策略。过滤式驱动程序安装在驱动栈一个或多个设备之上或下端,它可以截获设备、或设备类、或总线的请求,判断这些请求,并可以修改其内容或对其进行响应,例如USB键盘的高层过滤驱动程序可以增强加密检查,而适用于的低层类过滤有助于提高鼠标性能。
函数式驱动是这样一种结构:属于某类设备的常规执行可以通过其类驱动实现,即是说驱动程序在开发时,开发人员的工作只需要写出非常少的驱动代码minidriver去与硬件打交道,大部分工作可以通过调用类驱动完成。微软提供的类驱动可以实现常见的系统任务,比如即插即用和电源管理。WDM class drivers主要内容包括:
(1)流式类驱动,以内核模式支持多媒体内容;
(2)具有支持输入设备的HID类驱动;
(3)USB 和 IEEE 1394总线类驱动;
(4)支持串行和并行方式的存储协议。
Windows支持WDM驱动的各个系统内核组件包括:
(1)Kernel组件,指基本的同步、性能计算和及时、延缓与IRQ控制;
(2)Object Manager组件,对象说明;
(3)Executive执行组件,内存分配、互锁及列项操作;
(4)I/O 管理组件,包括I/O IRP(Request Packet)控制,设备对象,工作项目,注册表访问,系统状态提示,DMA及中断;
(5)内存管理,虚拟到物理内存映像,物理内存托管和锁定,驱动程序映像内存锁定,机动I/O空间;
(6)处理服务,系统线程生成和删除;
(7)Run-time Library,大容量外存,Unicode和数据类型转换;
(8)电源管理,电源状态改变,电源IRP控制,设备空闲检测;
(9)即插即用子系统,硬件检测和资源分配,PnP(Plug and Play) IRP控制以及硬件事件;
(10)WMI(Windows Management Instrumentation),用于支持设备测试以及检测指示数据的支撑结构;
(11)内核式流,是连接流数据设备的支撑结构;
(12)硬件提取层,提取平台,访问和调用I/O端口及内存映像设备。
电源管理可以是系统级或设备级,前者可以请求整个系统关闭。系统电源有六种状态:不可完全开启、完全关闭、三种休闲状态和一种休眠状态。设备级电源管理则有四种状态:完全开启、完全关闭,加两种休眠状态。一个设备可自行关闭,即使系统其它部分正全速运行。
驱动程序组成原理与研制要点
一个设备驱动程序的模块组成主要包括:初始化自己、创建和删除设备、处理Win32打开和关闭文件句柄的请求、处理Win32系统的I/O请求、对设备的串行化访问、访问硬件、调用其它驱动程序、取消I/O请求、超时I/O请求、处理一个PnP设备被加入或删除的情况、处理电源管理请求、调用Windows管理诊断WMI向系统管理员报告。
其中,"初始化"模块必不可少,所有驱动程序都需要通过分发例程处理用户的I/O请求。而WDM风格的设备驱动程序需要有"即插即用(PnP)"模块以及安装标识的INF文件。在上述不同模块之间需要交互,其中的一些交互可以直接通过函数调用,而大量信息需要通过数据结构传递(比如在"设备对象"之类的数据结构中可以存储每个设备的信息)。
分发例程指的是"创建"、"读"、"写"、"关闭"等处理程序,它可以执行对IRP的初始化处理,检查参数的合法性。在驱动程序编写中常遇到的非常棘手的所谓"同步问题",指的就是两个或更多的IRP分发例程往往要"同时"运行,它不仅在多处理器系统中会遇到,而且在单处理器系统中有时也很尖锐:比如单处理器系统上的一个分发例程在等待对低层驱动程序调用完成的同时,另一个IRP分发例程又被调用。处理这种冲突的机制主要有两种:一种是临界段例程,依此保证代码不会被中断处理程序中断;另一种机制是通过StartIo例程串行处理IRP,每个设备对象有一个内部的IRP队列,驱动程序的分发例程把IRP插入到这个设备队列中,内核I/O管理器从该队列一个个取出IRP,并传递到驱动程序的StartIo例程,StartIo例程串行处理IRP,以确保不与其它IRP处理程序冲突,但是StartIo例程仍然需要通过临界段例程避免与硬件中断发生冲突。
中断是用于停止处理器对一个任务的执行,而被强制运行某个中断去处理代码。中断包括软件中断和硬件中断。中断具有优先级,低优先级中断会被高优先级的中断所中断,以保证重要任务会优先执行。硬件产生的中断总是比软件产生的中断优先级要高。硬件中断类型包括:设备中断请求级处理程序的执行、配置文件定时器、时钟、同步器、处理器之间中断级、电源故障级;软件中断包括异步过程调用执行、线程调度、延迟过程调用执行。至于常规线程执行则没有中断。
驱动程序的主要的初始化入口点是一个称为DriverEntry的例程。多数WDM设备对象是由PnP管理器调用AddDevice入口点创建,该例程在插入新设备和安装INF文件指示相对应的驱动程序时被调用,之后,一系列PnP IRP被发送到驱动程序,指示设备应何时启动和查询其功能。为了让应用程序认知驱动设备存在,开发人员必须为每个设备对象创建使Win32可见的符号链接,这时可以有两种实现途径:一种是,采用显式的"硬编码"符号链接名,对此,应用程序的源代码中也具有设备名的硬编码;还有一种是采用设备接口,每个设备接口由一个GUID(全局唯一标识符)标识,对设备注册为具有某个特定的设备接口时就创建了一个符号链接。用户程序可以搜索有特定GUID的所有设备。驱动程序的代码是由系统内核通过发送I/O的IRP请求运行的。IRP是设备驱动程序的基本结构。
内核所调用的驱动程序中的其它多个例程就是所谓回调例程(Callback),每个回调例程具有一个标准的函数原型,它适用于对应的环境。常用的回调例程包括:Unload(卸载驱动程序)、AddDevice(添加一个新的PnP设备)、StartIo(串行处理IRP)、ISR(中断服务例程)、DpcForIsr(延时过程调用例程)、临界段例程、撤消IRP的Cancel例程、一个低层驱动程序完成一个IRP处理时所调用的Completion例程、当DMA通道可用时调用的AdapterControl、Timer(秒级定时器回调例程)、低于1秒的超时例程CustomTimerDpc、用于处理工作队列的CustomDpc、Reinitialize(用于初始化耗时很长的驱动程序)。
硬件驱动程序应当具有容错性,比如连接电缆断开或缓存区过载,并能够将错误信息反馈到应用程序。可以检查硬件状态位(如机缺纸指示),并能够处理快速I/O事件。
与其它Windows编程类似的是,在编写硬件驱动程序时如何分配或访问内存是一件很讲究细节的技术活。我们知道,Windows中采用的虚拟内存意味着系统可以使用比物理内存更多的内存。虚拟内存的实现方式是:将每个应用程序的可能地址空间划分成固定大小的块,这些块称为页(x86处理器的页大小为4KB,Alpha处理器的页为8KB)。页可以驻留在物理内存,也可交换到硬盘。设备驱动程序分配的内存通常有两种:可以是交换型的分页内存,也可以是永久驻留的非分页内存。如果试图在线程调度或更高中断级访问分页内存,就会引起缺页故障,造成内核崩溃。即使是常规线程,如果访问非驻留的分页内存,内核就会造成线程阻塞,直到内存管理器把内存装回内存。问题的关键是,在设计驱动程序时,一方面忌讳滥用非分页内存,另一方面却不得不经常使用非分页内存。因为要在线程调度或更高中断级访问内存,必须调用非分页内存。当完成内存调用后,应当对所有类型的内存通过ExFreePool释放。假如不释放内存,将会减少内存,因为即使当驱动程序卸载后,内核并不收集这些分配了的内存。
没有编写过硬件驱动程序的开发人员,在听到硬件驱动程序编程时,他们从直觉上会联想到需要与硬件资源打交道,包括I/O端口、中断、存储器地址和DMA(直接存储器访问)。其实,这是一种误解,因为有很多驱动程序不需要直接调用任何低层硬件资源,比如USB客户端驱动程序就不需要任何硬件资源,所有与硬件相关的琐细工作都有USB总线驱动程序完成,USB客户端驱动程序只是对总线驱动程序发出请求。
WDM的版本问题
虽然Windows XP/2000/98/Me都支持WDM,但是由于历史原因,不同版本WDM内容并不相同。当然,新版WDM都是旧版WDM的超集。跨系统使用的WDM driver通常采用IoIsWdmVersionAvailable例程去判定当前运行系统支持WDM的版本号。按照常理说,保证跨平台兼容性的最简单的方式应该是:写一个驱动程序时仅包括最低版本WDM所支持的那些功能。但是,这种思路往往行不通,因为驱动程序不仅要适应不同的OS,而且还应当具有发挥具体系统特色优势的附加代码。
最新编写出的内核模式的驱动程序应该属于WDM类型,其开发平台则应为Windows XP。任何WDM drivers都必须支持PnP、电源管理,并能执行WMI。一般的原则是,即使是用于更低版本的驱动程序,也最好首先在Windows XP下开发然后做移植,这种策略同样适用于那些并不完全适合WDM模式的硬件设备。如果要写出WDM驱动程序,开发人员必须通过最新的Windows DDK了解不同Windows平台的差异,以及总线和设备的相关问题。比如:其一,不同Windows平台的驱动程序代码执行会不同,主要由于WDM兼容了Windows XP/ 2000/ 98/Me中的不同系统结构。在其中一个平台工作正常的driver,到其它平台时需要全面测试,尤其用于多处理器系统时要特别注意。其二,WDM并不支持所有类型硬件。其三,INF文件必须适应平台之间的差异。
阅读(1343) | 评论(0) | 转发(0) |