Windows 编程/设备驱动程序简介
Windows 设备驱动程序通常有两种类型:虚拟设备驱动程序 (VXD) 和Windows 驱动程序模型 (WDM)。VxD 样式驱动程序比较旧,兼容性较差,而 WDM 驱动程序应该可以完全代码兼容,一直追溯到 Windows 98。
在 DOS 的早期,计算机是自由之地,一切皆有可能。为此,开发者编写了自己的硬件驱动程序,不符合任何特定规范或接口,使用实模式汇编代码。随着 Windows 3.0 的出现,操作系统开始对应用程序管理采取更加积极主动的方式,通过创建和维护各种虚拟机,在不同的处理器上下文中执行不同的程序。驱动程序不再能够以不符合规范的实模式 DOS 驱动程序存在,而是必须减轻多个程序之间的访问,这些程序或多或少并行运行。Windows 3.0 将“真实设备”更改为称为“虚拟设备”的受管理资源,并将实模式驱动程序替换为新的虚拟设备驱动程序 (VDD)。
Windows NT 产品线作为一个独立的实体存在于“普通”Windows 品牌之外。这两个操作系统在几乎所有你能想到的方面都完全不同,除了外壳看起来相似。Windows NT 是一个完全受管理的操作系统,NT 内核阻止未经授权的资源访问。这意味着在 Windows NT 中,设备驱动程序需要通过特定方法与计算机交互,而标准的 Windows 驱动程序(Windows 3.0、3.1、3.11、95、98、Me)可以直接访问硬件,无需任何管理。此时,这两个系统的驱动程序通常也用汇编语言编写。
认识到市场在 Windows 和 Windows NT 之间分裂,微软看到了引入单一驱动程序模型的必要性,以便设备驱动程序可以在 Windows 和 Windows NT 之间移植。此外,微软知道驱动程序必须用 C 语言等高级语言编写,以便能够在不同的硬件系统中代码兼容。为了满足这些需求,微软创建了Windows 驱动程序模型 (WDM)。WDM 驱动程序使用 DDK 编译,用 C 语言编写,并遵循严格的规范,确保它们可以在任何 Windows 系统上执行。本书将尝试专注于 WDM 驱动程序,但也将包含有关编写 DOS TSR 驱动程序和 VDD 的说明。
设备驱动程序在内核模式下运行,因此编写、测试和调试驱动程序可能是一项棘手的任务。驱动程序应在安装之前始终经过充分测试。
由于设备驱动程序不在用户模式下运行,因此用户模式库(kernel32.dll、user32.dll、wingdi.dll、msvcrt.dll)不可用于设备驱动程序。相反,设备驱动程序必须直接链接到 ntoskrnl.exe 和 hal.dll,它们提供本机 API 和执行服务。
设备驱动程序通常用 C 语言编写,使用驱动程序开发工具包 (DDK)。根据所选择的编程语言,可以使用函数式和面向对象的方式来编写驱动程序。通常无法使用 Visual Basic 或其他高级语言来编写驱动程序。
由于驱动程序在内核模式下运行,因此对驱动程序可以执行的操作没有限制。驱动程序可以读取和写入内存的受保护区域,可以直接访问 I/O 端口,并且通常可以执行各种非常强大的操作。这种能力使驱动程序能够以极高的效率破坏一个原本稳定的系统。
Windows 平台 DDK 附带头文件、库文件和一个命令行编译器,可用于用 C 或 C++ 编写设备驱动程序。DDK 编译器没有图形界面。
Windows 以高度模块化的方式实现设备驱动程序,在我们继续讨论驱动程序编程之前,我们必须讨论一些术语。任何特定设备所需的驱动程序都按驱动程序堆栈排列,并通过一个单向链表在内部连接在一起,该链表从堆栈底部(根驱动程序)开始,在最高级驱动程序处终止。每个驱动程序必须包含至少两个模块,一个根驱动程序和一个功能驱动程序。这种组合,加上一些可选的补充,构成了人们通常称为完整的“设备驱动程序”的全部内容。功能驱动程序将是最常见的编写类型,并且将是本维基教科书的重点。
微软认识到,某些类型的设备都以类似的方式工作,如果每个硬件制造商都不得不从头开始编写所有驱动程序代码,这将是巨大的时间浪费。为此,Windows 允许使用一种称为类驱动程序的驱动程序类型。类驱动程序本身不是完整的功能驱动程序,但类驱动程序可以动态链接到常规功能驱动程序,并且可以简化开发过程。可以编写自己的类驱动程序,但第三方程序员通常不用担心这一点。通常,微软会提供类驱动程序,驱动程序开发者会利用这些类驱动程序。这确保了类驱动程序完全经过微软测试和认证,并且非常通用。
驱动程序的另一个分类是筛选器驱动程序。筛选器驱动程序通常有两种类型,一种是上层筛选器驱动程序,另一种是下层筛选器驱动程序。上层筛选器驱动程序位于功能驱动程序堆栈之上,顾名思义,它们会过滤传入的 I/O 请求。下层筛选器驱动程序位于功能驱动程序和根驱动程序之间的堆栈中。筛选器驱动程序通常作为错误修复或对现有驱动程序的快速 hack 扩展来实现。
以下是驱动程序堆栈的通用示意图
Upper filter driver | | Function Driver <-------> Class Driver | | Lower Filter Driver | | Root driver | | Hardware
为了简化,让我们使用术语“总线”来指代计算机上任何可以从一个地方传输到另一个地方的信息的地方。这是一个非常宽泛的定义,并且理应如此:“总线”一词需要涵盖从 USB、串行端口、PCI 卡、视频输出等等所有内容。每个总线都由其自己的根驱动程序控制。有一个 USB 根驱动程序、一个 PCI 根驱动程序等等。
现在让我们考虑一种名为根总线的神话构造,它是一个所有其他总线都连接到的结构。根总线对象实际上并不存在于你的计算机中,但将其视为一个方便的思考方式。此外,根总线也有自己的驱动程序。根总线驱动程序对象负责跟踪连接到你整个计算机中任何总线上的设备,并确保数据到达目的地。
即插即用 (PnP) 是一种技术,允许计算机上的硬件动态更改,PnP 软件会自动检测更改,并分配重要的系统资源。PnP 有自己的根驱动程序,该驱动程序与根总线驱动程序密切通信,以跟踪系统中的设备。
驱动程序在调用驱动程序时运行的任何线程的上下文中执行。为此,我们说驱动程序在“任意上下文”中执行。因此,驱动程序程序员不应该对驱动程序入口点的处理器状态做任何假设。这会带来一些问题,所以我们将在本文中讨论它们。
想要使用 MMX 或浮点运算的驱动程序可能会发现它们遇到了一些不必要的困难。因为驱动程序可以在任何时间在任何上下文中进入,所以浮点单元可能包含来自中断以调用驱动程序的用户模式程序的部分结果和未处理的异常。仅仅保存上下文然后恢复它是不够的,因为任何未处理的异常都可能变成“无法处理”,并引发系统错误或错误检查。微软只在某些情况下建议使用浮点运算,我们将在后面讨论它们。
- 了解 Windows 驱动程序模型 - 关于 WDM 编程所需的必要基本概念的介绍
- WDM I/O 概念 - 了解 WDM 编程所需的 I/O 概念
- 内核模式驱动程序框架 1.11 - .ISO 下载包含驱动程序开发工具包 (DDK)