分类:
2010-06-01 14:37:05
Programming the Microsoft Windows Driver Model,Second Edition
Walter Oney
第一章 开始一个驱动项目
在这章里,我会概述一下驱动的编写流程。当20世纪80年代IBM将MS—DOS作为他们个人电脑的操作系统时,我包括 个人计算就是从那时开始的。当时IBM和Mircosoft的决定现在仍然能够体会到,因此一些过去的看法会帮助的理解驱动的编写。
Windows driver model(WDM)驱动程序运行在两个完全不同的操作系统环境中。并且我将在这章概述一下这些环境的体系结构。Windows XP,像windows2000和更早期的windows版本一样,提供了一个正式的框架结构使驱动在执行代表应用程序和其他驱动的I/O操作时能执行一 个被明确定义的任务。Windows Me,像windows9x和windows3.x之前的版本,是一个驱动程序能做更多任务的更加随心所欲的系统。
任何一个驱动项目的第一步都是先要确定你要写一个什么样的驱动——你是否真正需要写一个。为了帮你做这个决定,我在这章 里记述了对许多种不同设备的见解。
最后我还会列出一个清单来帮助你来熟悉项目的领域范围。
1.1设备驱动程序简史
最
早的运行在Intel处理器芯片上的PC机,CPU提供给其640KB的所谓的“实”内存。这是因为内存是真正在那里以内存芯片的形式存在,并且可以依靠
处理器的20位物理地址直接访问的。而处理器也只提供了一种模式,称之为实模式,其内部由处理器将两个16位的寄存器结合,组成20位的内存地址形式,给
相应的涉及内存操作的指令使用。计算机体系结构高扩了扩充插槽的概念使得一些用户可以将买来的独立的板卡组装到机器上。这类板卡常常伴随有一些用于设置
DIP开关的指令(随后,跳线出现),这些指令可以轻微地改变I/O配置。但你不得不了解你PC的所有I/O和中断分配来正确的做这件事情。MS-DOS
基于CONFIG.SYS文件整合起了一种架构,使得操作系统能够装载原始设备和附加板卡的实模式设备驱动程序。所以,不可避免的就是这些驱动程序要使用
汇编语言来编写并且或多或少地依赖于一定的BIOS中断指令和MS-DOS自身的系统服务。这样,最终用户不得不通过命令调用应用程序。而应用程序开发者
也不得不为程序开发出视频显示,键盘,和鼠标程序,因为MS-DOS和系统BIOS都没有把其做的充分。
之后,IBM引入了基于80286处理器
的AT系列的个人计算机。286处理器添加了保护模式作业,这样,这样程序可以访问的主内存和扩展内存达到16MB,其使用24位段地址(通过一个在16
位段寄存器中的段选择器被间接指定)和一个16位的偏移地址。MS-DOS自己保持为一个实模式操作系统,所以当时许多软件厂商做过一些DOS的扩展产品
来允许程序员将其实模式下的应用程序移到保护模式下并能访问到全部内存。自MS-DOS开始统治电脑以来,驱动程序技术基本上就没有前进。
改变PC机技术的分水岭在我看来,应当是——Intel发布其80386处理器芯片之日。386允许程序通过页表间接地 访问访问高达4GB的虚拟内存地址,它还允许程序不费吹灰之力就能将32位数据用于算法和寻址上。那一阵子曾经让软件工具市场很狼狈,如编译器厂商和 DOS扩展产品的公司…………设备驱动程序依旧是用汇编语言写的16位的实模式程序并且通过CONFIG.SYS安装,而终端用户依旧需要手动配置板卡
之后的处理器主要是在性能和速度上的提升,本质上并没有改变。如我写这章的时候,1GHz的CPU,50G的硬盘和 512MB(乃至更高)的内存的计算机已经司空见惯了。
与之同时,操作系统平台上的反展也进行着。大多数人,甚至包括系统程序员,也更加喜欢图形交互式的计算机而不是字符式的 了。实际上Microsoft在图形化操作系统上并没有抢先一步,Apple在其第一款Macintosh上倒是先发制人。但之后微软却用其 Windows系列操作系统在这个领域上取得了霸主地位。一开始,Windows不过是一个实模式的MS-DOS下的一个图形模式的外壳,随着时间的过 去,一批包括显示,键盘和鼠标的通用Windows驱动出现了。这些驱动是可执行文件,其扩展名为.DRV,主要使用汇编语言编写。
随着AT系列电脑的问世,Microsoft生产了一个有保护模式版本的Windows。但Microsoft还是在保 护模式下使用实模式下的.DRV驱动。硬件除了标准的Windows设备(如显示,键盘和鼠标)外,继续沿用实模式下的MS-DOS驱动。
最终,在386处理器时代后不久,Micorsoft发布了Windows3.0,一个完全利用虚拟内存方式的使用“增
强”模式作业的操作系统。虽然如此,但其新的硬件依旧需要实模式驱动。但现在已经有了大问题。若要多任务地执行MS-DOS程序(这是用户所需要
的),Micorsoft不得不建立虚拟操作系统。每个MS-DOS应用程序在Windows的图形环境上运行在其自己的虚拟机上。但是所有这类的MS-
DOS应用程序都可能会通过IN和OUT指令来直接访问硬件,读写设备存储器,处理来自硬件的中断。此外,两个或更多的应用程序共享处理器时间片将会导致
硬件上的指令冲突。这类冲突当然也会在显示,键盘和鼠标上表现出来。
为了允许多种程序共享物理硬件,Microsoft引入了虚拟驱动设备的概
念,其主要的目的是“虚拟”一个硬件设备。这样驱动被统一称之为VxD,因为其大部分的文件名适合VxD.386的特征。其中,x代表其管理的设备。利用
这一概念,Windows
3.0创建了可以单独使用许多硬件设备的虚拟机外观。但由于设备自身的发展,大多数的时候它还得被实模式的MS-DOS驱动程序所驱动。VxD就扮演了
通过首先截取应用程序试图接触硬件的尝试,把应用程序的访问传递给了硬件,并暂时地将处理器切换到一种被称为虚拟8086的实模式下来运行MS-DOS驱
动程序的这种操作。
这样其实也不是很好,使用模式切换来运行实模式的驱动……
Microsoft版本的OS/2最后成为了Windows
NT,其第一次公开发行是在刚刚发布完Windows 3.1的上个世纪90年代。Microsoft构建Windows
NT系统是想让其成为一种稳定和安全的平台。Windows
NT的驱动使用一种崭新的内核模式技术。实际上其几乎没有参与当时流行的其它的两种驱动技术。Windows
NT驱动程序完全使用了C语言编写,这样就可以在不改变任何代码的情况下在新的CPU架构上被重新编译。
另一件有关Windows 3.0 发展的事情,这已经成为我们今天的一个重要的分支。Windows 正式将软件世界分成了用户模式程序和内核模式程序。用户模式程序包括了用户买来使用的所有的应用程序和游戏。但其并不被信任能够健壮地(甚至是诚实地)来 处理硬件或者其他程序。而内核模式程序则包括操作系统自身和大家写的全部的驱动程序。内核模式程序是被系统完全信赖的,并且可以接触到它们想要的系统的任 何资源。虽然 Windows 3.0使用了其作业模式的隔离程序,但没有一个版本的Windows(甚至包括Windows Me)真正地引入内存保护使其成为一个安全的系统。安全是Windows NT和其后继者的主要职责,如禁止用户模式程序查看或者更改由内核管理的资源。
实际上PC机的平均计算能力直到最近才满足了Windows NT良好运行地需要。因此Microsoft不得不延长其它Windows的产品的使用周期。Windows 3.0成长为 3.1,3.11,和95。以Windows 95开始,如果你打算编写一个设备驱动,你就会去写一种被称为VxD的真正的32位保护模式的驱动程序。同样,从 Windows 95开始,终端用户可以丢掉他们的I/O图,因为操作系统新的即插即用的特点可以开始自动地识别并配置硬件了。但是作为一个硬件制造者,你还不得不为了那 些不打算从Windows 3.X升级的用户编写一个实模式驱动。同时,Windows NT发展到了3.5,4.0。你可能需要第三方的驱动来支持这些系统,而且你需要在这些项目中使用你的编程知识。
之后,Micorsoft又开发了一个新的设备驱动技术——Windows 驱动模块(WDM),并且将其应用到了Windows 95后的Windows 98和Windows Me。它们也将此技术应用到了Windows NT后的Windows 2000和Windows XP。在Windows Me时,MS-DOS只是象征性的存在,并且不需要硬件制造者担心实模式的设备驱动。因为有WDM在,至少由原始的意图,实际同样在所有的平台上,写一个 这样的驱动成为了可能。
总之,我们是站在最初的PC架构和早期的MS-DOS的基础上的。最终用户仍还会打开机箱,安装一些扩展的板卡,但和以 前相比,现在我们可以使用一些不同的、更强有力的总线。即插即用和周边元件扩展接口(PCI)总线已让最终用户远离了I/O,内存和中断请求的使用。虽然 现在还有BIOS,但其现在的主要作用只是引导系统并告诉操作系统(Windows XP或Windows Me)那些被发现的配置细节。WDM驱动程序的扩展名是.SYS,就和当初实模式下的驱动程序一样。
1.2 操作系统概述
Windows
driver
model为驱动程序运行在两种操作系统中提供了一个结构框架:Windows98,windowsMe和windowsXP,windows2000。
在之前的历史摘要中所讨论的那样,这两种操作系统是两种平行的发展方向的产物。实际上,我将之前的这个系统称为98/Me是为了强调他们共同的传统,而后
来的这个系统则简单称之为XP了。虽然对最终用户来说这两种系统很类似,但他们的内部工作十分不同,在一节中,我将对这两套系统进行简单概述。
XP概述
图1-1是一张Windows XP操作系统的简略的逻辑图, 图里我对写设备驱动的人强调了一些重要的特点。 Windows XP对每个平台都支持两种执行模式。 软件既在用户模式又在内核模式下运行。 一个用户模式下的程序想要从一个设备中读取一些数据,他就会向application programming interface(API)发送一个请求,比如说发送“readfile”。而子系统模块(像KENRNEL32.DLL)通过调用一个本地的API功 能(像NtReadFile)来实现。有关本地API的信息的请参考下面。(The native API)
我们经常所说NtReadFile是被一个称为I/O Manager的系统部件的一部分。 I/O Manager这个术语可能稍使人误解,因为并不存在这个的单独可执行模件。 当我们讨论围绕着驱动程序的操作系统服务的不明白的地方的时候,我们就需要要使用的一个名字。而“I/O管理器”就是我们通常使用的名字。
很多程序服务类似于NtReadFile。他们在内核态里运行,它响应了一个应用的请求后,用某种方式来与一个设备交 互。 他们验证所有的参数, 因此不用担心因为执行了某个操作安全性得不到保障, 或者防止用户模式程序非法存取数据。 然后他们创建了一个名为I/O请求包(IRP)的数据结构,并把这个数据结构送到某个驱动程序的入口点。 比如以一个最初的ReadFile请求来说,NtReadFile 将创建一个IRP,它带有主要功能代码IRP_MJ_READ(在DDK驱动开发工具包的头文件中是一个常量)。 实际的处理细节可能不同,但大体上都会像NtReadFile这样:它会返回一个状态告诉用户模式发送请求者操作还没有完成。设备驱动也许会运行其他应用 程序来等待操作完成,或者立即进入等待状态。不论哪种方式,设备驱动程序对该IRP的处理都与应用程序无关。
执行IRP的设备驱动程序最后可能会访问硬件。对于一个programmed I/O
(PIO)方式的设备,IRP_MJ_READ操作将导致直接读取设备的端口或者是设备实现的内存寄存器。尽管运行在内核模式中的驱动程序可以直接与硬件
对话,但它们通常都使用硬件抽象层(HAL)访问硬件。读操作包括使用READ_PORT_UCHAR从某个I/O口读取单字节数据。HAL例程使用平台
相关的方法执行操作。在Intel x86计算机上,HAL使用IN指令(访问设备端口),在以后的xp平台,很可能使用内存fetch
驱动程序完成一个I/O操作后,通过调用一个特殊的内核模式服务例程来完成该IRP。完成操作是处理IRP的最后动作, 它使等待的应用程序恢复运行。
1.1图请参照原文
The native API
NtReadFile是Windows XP中称为native API的一部分。 叫做native API也是有原因的。 最初的Windows NT操作系统包含许多子系统来实现几个新的和现有的操作系统的语义。 像有OS / 2子系统,POSIX 子系统和Win32 子系统。 用户模式产生的呼叫通过the native API来执行子系统,而子系统在核心态内执行。
一个user-mode DLL 中叫做(我认为多余)NTDLL. DLL 为Win32的呼叫者执行了native API。 每次DLL都带来一个呼叫请求一个内核模式功能同时执行这个功能。呼叫使用了……系统服务接口在用户模式和内核模式间传递控制命令。新的Intel 处理器,这个系统服务接口使用的是SYSENTER指令。 旧的Intel 处理器,接口使用的有功能代码0x 2E使用内部的指令。 在其他处理器上,还使用了其他的机制。 可是,我们写驱动程序不需要理解它机制的细节。 但是, 我们需要了解的是“这种机制允许运行在用户模式下的程序呼叫运行在内核模式下的子程序,而这个子程序最后会返回给用户模式的呼叫者”的这个过程。在这个过 程中没有线上下文切换发生: 所有的变化只是执行代码的权限级别(连同其他只有汇编语言程序员才会注意到或关心一些其他细节)。
Win32 子系统是大多数应用程序员都熟悉的,因为它是与与Windows图形用户界面一起执行一般功能的。 其它子系统过时而被淘汰了, the native API却保留了下来,不过,Win32系统还在继续使用,我在正文中将举例说明。
98/me概述
图1-2显示了Windows 98/me的基本结构。其操作系统内核称为虚拟机管理器(VMM),因为它的主要工作就是创建一个或多个“虚拟”机器,这些虚拟机器共享同一个物理机器的 硬件。Windows 3.0引入虚拟设备驱动程序(VxD)的原始目的就是为了虚化设备,以帮助VMM实现每个虚拟机器都拥有全部硬件的假象。VMM架构也被引入 Windows 98/me,并能处理新硬件和32位应用程序。
1.2图请参照原文
Windows 98/me不能象Windows xp那样整洁地处理I/O操作。主要的不同在于Windows 98/me处理磁盘操作、通讯口操作、键盘操作,等等方面。Windows 98/me以两种完全不同的方式为32位应用程序和16位应用程序提供服务。见图1-3。
图1-3. Windows 98中的I/O请求 参照原文
图1-3的左侧显示了32位应用程序的I/O请求处理过程。应用程序调用的Win32API(例如ReadFile)是 系统DLL(如KERNEL32.DLL)中的服务例程,但应用程序仅能用ReadFile读磁盘文件、通讯口,和有WDM驱动程序的设备。对于其它种设 备,应用程序必须使用基于DeviceIoControl的特殊方式。并且Windows 98的系统DLL含有与Windows xp不同的代码。ReadFile的用户模式部分(如参数检验,Windows xp在内核中实现)使用某个专用机制到达内核模式驱动程序。磁盘文件操作使用一种机制,串行口操作使用另一种机制,而WDM设备也有自己专用的机制进入内 核。所有这些机制都利用软件中断30h来实现用户模式到内核模式的转换,但它们之间又完全不同。
图1-3的中间显示了16位Windows应用程序的I/O请求处理过程,右侧是MS-DOS应用程序的I/O请求处理 过程。在这两种形式中,用户模式应用程序直接或间接地调用了用户模式的驱动程序,原理上,这些用户模式驱动程序可以直接操作机器硬件而不用其它系统部件支 持。例如,Win16程序通过调用名为COMM.DRV的16位DLL间接地执行串行口I/O。(到Windows 95为止,COMM.DRV仍是一个单独的驱动程序,它挂在IRQ3和IRQ4上,直接向串行口芯片发出IN和OUT指令) 虚拟通信(指虚拟机器之间的沟通)设备(VCD)驱动程序通过截获I/O端口操作来保证两个虚拟机不同时访问相同的端口。如果以一种神秘的方式思考这个过 程,你可以这样认为,用户模式驱动程序使用了一个基于I/O截获操作的“API”,象VCD这样的“虚拟化”驱动程序就是通过冒充硬件操作来实现假API 服务的。
Windows xp的所有内核模式I/O操作都使用一个公用的数据结构(IRP)。而Windows 98/me没有达到这样高度统一,甚至当应用程序的请求到达内核模式的时,其串行口驱动程序要遵从由VCOMM.VXD规定的port驱动程序函数调用规 范,而磁盘驱动程序则遵从IOS.VXD实现的包驱动层次架构。其它设备类驱动程序也有其它的实现方式。
如果要把WDM引入Windows 98/me,就必须使Windows 98/me内部架构与Windows xp非常类似。Windows 98/me包含了NTKERN.VXD(VMM32.VXD)系统模块,该模块含有大量Windows NT内核支持函数的Windows实现。NTKERN.VXD使用与Windows xp相同的方式创建IRP并发送IRP到WDM驱动程序。实际上,WDM驱动程序几乎区别不出这两个环境的不同。
1.3我们需要什么样的驱动?
许多种的驱动组成了Windows XP系统。如图1-4所示。
虚拟设备驱动(VDD)是一个用户模式组件,它允许基于微软DOS操作系统的硬件接入基于英特尔x86平台.一个虚拟设 备驱动是基于输入允许掩码来使管脚接入,它基本上是模拟了硬件的运行方式使接入设备在能够告诉在裸机上面的硬件设备如何来运行。不要将WindowsXP 中的VDD和Windows98/me中的VxD相混淆。他们都叫做虚拟设备驱动程序,他们都是被虚拟硬件来提供服务,但是他们是基于不同的软件技术。
?
核心模式驱动程序目包括许多子目录。PnP是一种核心模式驱动程序它能使WindowsXP识别热插拔的协议。非常正确 这个书只介绍关于热插拔驱动程序。
WMD是一个能量管理协议的PnP驱动程序,它可以应用与Windows98/Me和Windows2000/XP。在 WMD这个目录中,你能去认出类驱动程序(他负责管理那些为大家非常熟悉的设备)迷你驱动(用来支持一类特殊的驱动)单独功能的驱动程序(他期间在需要支 持硬件设备的功能方面)过滤驱动程序(它用来过滤一些特殊设备输入输出操作来增加和改变的运行方式)。
文件系统驱动使本地的硬盘和通过网络连接实现标准的微机文件系统模式(他包含名字文件的管
理结构)这些也是核心模式驱动程序。
?
合法部件驱动是核心模式驱动,它直接控制硬件设备而不需要其他的驱动。
此目录包括的驱动是windows NT的早期的版本,它可以不要改动就在windows XP中运行。
并不是所有的分类都很重要,在我以前的书中提到的Windows 95系统程序设计(微软出版社,1996),如果你买了我的书就不会感到困惑了。特别的,我并不是总是仔细的用以前的分类来进新严格区分WDM和PnP驱 动。主要的分类是按照驱动是否能在Windows 2000/XP和Windows 98/95中运行了进行。没必要使用严格的分类方法,我将非常仔细的讨论随后出现的系统组件。
面对这所有类别的驱动,一个写驱动的新手或是管理者可能会感到困惑,一个硬件需要哪一类的驱动。对于许多的设备,你根本 不用写任何的驱动因为微软已经自带了一个通用的驱动可以使你的设备工作。下面是一些例子:
?
SCSI-试配或是ATAPI-试配大容量存贮装置
任何连接有USB接口的设备都完全兼容一个改进的规范,可以使你在标准的微软的驱动下愉快而没有任何局限的使用。
标准串行或PS/2接口的鼠标
标准键盘
无需其他装置或加速装置的视频试配器
标准串行和并行接口
标准软驱
?
WDM驱动
大多数的设备微软并不直接支持,你需要写一个WDM驱动。首先你要决定是否要写一个单独功能的驱动,一个过滤驱动,或是 只是一个迷你驱动。你基本上不需要写一个类驱动因为微软喜欢保留它自己独特的驱动来服务于最广大的硬件厂商。
?
WDM 迷你驱动
基本的规则是如果微软已经为你想支持的一类设备写好了一个类驱动,你可以写一个迷你驱动在雷驱动下工作。你的迷你驱动名 义上是负责这个设备,但是你要调用类驱动中的子程序来实现对硬件的基本管理然后返回去做依赖多设备的工作。在迷你驱动中你所要作的大量的工作是在一类设备 和另一类设备中进行切换。
?
下面是你写一个迷你驱动时要用到的设备分类的例子:
非USB人输入装置(HID),包括鼠标,键盘,操纵杆,方向盘,等等。如果你有一个USB设备,通用的 HIDUSB.SYS(微软为USB HID设备写的驱动)是不够的,你也要写一个HIDCLASS迷你驱动。这类设备的主要特点是通过报告的方法报告用户可以被描述符数据结构来描述。为这一 类的设备,HIDCLASS服务于类驱动同时为直接输入和其他更高层的软件提供需多的功能,所以你要慎重使用HIDCLASS.SYS.这是非常难的稍后 我将用大量的篇幅来接受。HIDUSB.SYS是一个HIDCLSASS的迷你驱动。
?
Windows 图形获得设备(WIA),包括扫描仪和照相机。你需要写一个基于使用COM类接口的WIA迷你驱动来为你的硬件提供特别的支持。
流设备,如音频,DVD,视频设备,和只能使用软件过滤的多媒体数据流。你需要写一个流迷你驱动。
?
非传统总线的网络设备接口,如USB和1394。像这样的设备,你需要写一个网络设备接口规范(NDIS)迷你驱动,使 用像在DDK文档中中提到的类似的语句。这种驱动不能在两种系统中进行切换,所以你需要针对不同的平台编写不同的迷你驱动。
视频卡。这些设备需要一个视频迷你驱动来通过视频接口类驱动工作。
打印机,需要用户模式的DLLs来代替核心模式驱动。
电池,微软为次提供了一个通用的类驱动,你需要写一个迷你驱动(DDK叫它迷你类驱动,其实是一样的)来使 BATTC.SYS工作。
?
WDM过滤驱动
也许你有一个设备,它在标准的微软的驱动程序下就可以运行的很不错了。
在一些情况下,你也许要写一个过滤驱动来改变标准驱动才能使你的设备正常的工作。顺便说一下,这种情况并不是经常发生, 因为更改一个连接硬件的标准的驱动程序并不是很容易。
?
单独功能的WDM驱动
在下面的一节中有一些例外的需要注意,很多的其他类的设备需要我说的这种单独功能的WDM驱动。像一个驱动单独的处理所 有控制你的硬件的信息。
当这类驱动是合适的时候,我推荐下面的途径可以使你结束使用单一的二进制而可以在所有的因特尔x86平台下的所有操作系 统工作。首先,使用最新的DDK-我使用的是测试版的.NET DDK编写的例子。你可以使用IoIsWdmVersionAvailable来决定你要使用的操作系统。如果你的系统是Windows 2000/XP,你可以访问MmGetSystemRoutineAddress来得到一个只在Windows xp系统中使用功能。我同时建议你装一下WDMSTUB.SYS,这将会在附录A中讨论,来详细说明在Windows 98/Me中MmGetSystemRoutineAddress和其他核心功能;否则,你的驱动将不能在Window 98/Me中使用,因为没有定义输入。
?
这有几个写单独功能WDM驱动的例子:
几种串行接口的的读卡器
数模转换器
ISA卡支持独立识别读写传感器
?
更多的二元兼容
原来,WDM过去可意兼容所有版本的Windows操作系统。因为发行计划和更多的考虑,自从Windows 98以后发行的版本中已经包括了支持月来越多的很有用的核心功能,有些时候甚至是基本的,为了增强和方便程序设计。有一个例子是IoXxxWork- Item家族的功能,在第14章中介绍,它被加入了Windows 2000中用来取代缺乏稳定性的ExXxxWorkItem 系列。如果你不作其他的,一个叫做IoXxxWorkItem的例子不能在Windows 98/Me中使用,因为操作系统不能导入它的功能。MmGetSystemRoutineAddress是另一个不能在Windows 98/Me中使用的功能,不幸的是,你甚至不能在运行时调用相关的功能项。如果它的实现不理想,WHQL测试使所有的驱动标记访问 ExXxxWorkItem functions.功能。
?
在Windows 98/Me中,一种名为NTKERN的VxD实现了WDM的某些核心的支持功能。在附录中会有更详细的讨论,NTKERN依赖一个定义了的输入符号来实现 随时的载入。你可以定义你自己的输入符号,这就是WDMSTUB怎样来管理我建议你自己的二项兼容的驱动中使用的未定义的符号。
这个伴随的目录中包括WDMCHECK的效用,你可以运行在Windows 98/Me系统中来检查一个驱动丢失的输入信号。如果你已经开发出了一个在Windows XP中运行的相当好的驱动,我建议你先将它拷贝到Windows 98/Me系统中在WDMCHECK中运行。如果WDMCHECK显示你的驱动调用一些不支持的功能,然后再检查WDMSTUB是否支持这些功能。如果 是,只要将WDMSTUB加入到你的驱动包里,将会在附录中介绍。如果不行,修改你的驱动或是发E-Mail问我如何修改WDMSTUB。通过这些你可以 最终结束一个二元兼容的驱动。
?
其他类型的驱动
因为Windows 98/Me和Windows 2000/XP结构之间的差别一些情况下单独功能的WDM驱动不能运行。在以下的情况中,你需要写两个驱动:一个Windows 2000/XP下的WDM驱动和在Windows 98/Me的VxD驱动。
?
一个串行接口驱动。Windows 98/Me的驱动VxD在上升沿提供了VCOMM端口驱动接口,而Windows 2000/XP驱动是一个WDM驱动,在上升沿提供了一个充足和严格定义的IOCTL接口。这两种上升沿定义是明显不同的。
?
连接串口的设备的驱动。Windows 98/Me的驱动VxD呼叫VCOMM来和端口对话。而Windows 2000/XP驱动是一个WDM驱动和SERIAL.SYS对话或者其他几个串口驱动来实现类似IOCTL的接口。
一个为非标准的USB大容量存储器提供的驱动。为了Windows 98/Me,你要写一个VxD适合输入输出分层管理的多层驱动。为了Windows 2000/XP,你要写一个单独功能的WDM驱动在上升沿接受SCSI的请求块在下降沿和USB设备来进行传递。
?
为这两类设备,在WDM之前微软定义了一个可适应的驱动架构:
小型机系统接口(SCSI)适配器使用一个“SCSI 迷你接口”驱动,它不使用任何标准的核心支持功能而是依赖于从SCSI.SYS或是SCSIPORT.VXD输出的特殊的API代替,这种迷你接口可以使 用于系统之间。
?
网络接口卡使用一中“NDIS迷你接口驱动”它是依赖于从NDIS.SYS和NDIS.VXD输出的特别定义的一种 API,来实现相应的功能。过去,NDIS迷你接口驱动可以在系统之间使用,但是现在几乎不使用了。网络协议驱动和被叫做“媒介”的驱动提供了过滤的功能 和网络驱动接口标准。
1.4 概要管理和检查列表
如果你是一个(程序)开发管理者,除非你不必为“将硬件设备投入市场”负责,否则有一些有关设备驱动程序的情况你需要了解。首先,你需要决定:是否你需要
一个(贴近)用户的驱动程序,还要决定其类型。(文章)之前的部分应该已经帮你做了这样的决定,但是你可能还需要雇佣一个专业的咨询顾问(以便)在这方面
给你最直接的忠告和建议。
如果你经过评估之后,自信需要一个用户化的驱动程序,之后,你就需要找到合适的程序员。严酷的事实是:WDM驱动程序设计确实有些难,并且只有资深的程序
员有能力把它做好。很多公司都需要这种程序员,但是大多数(公司)都雇佣不起。如果你是这种情况的话,你就有两个基本选择:培训那些已经在你的团队的人
员,雇用一个程序员--他已经具备了必要的能力--作为咨询顾问或者合同程序员,或者从一个专业的驱动程序设计公司寻求支持。你需要根据自身的个性需要来
衡量这些选择,(以便)从中进行挑选。
一旦,合理确切的说明了硬件将如何运转,驱动程序设计就应该开始了。你应该做好这样的心理准备:在驱动程序开发过程中(会)暴露一些讨厌的(新)发现,
(此时)说明(文档)(也应该做相应的)修改;你还应该料想到你的硬件、硬件/软件接口和驱动程序的蓝图将被多次的重复(修改)。
而且,驱动程序设计将比你最初预想的经历更长的时间并且消耗更多的投入。所有的软件都遭受时间和金钱的(双重)开销。驱动程序设计的这种附加消耗主要是以
下原因(造成):硬件和软件人员之间的沟通困难,在说明(文档)和DDK文件(内容中的)歧义,所有部件中(可能出现的)程序错误,以及设计和生产中的延
迟。
在大多数情况下,你将使你的硬件和软件服从于“视窗硬件品质保障(WHQL)”以便获得顺利安装的数字签名和获准成为微软logo目录中的一员。你将自己
进行大量的测试,并且你将需要具体的计算机安装(程序)以便完成(测试),因此,为你的硬件部件找出最初的测试需求不可避免的成为了你工程的最后部
分。(举个例子:测试一个USB设备,要求你具备一个具体拓扑结构(计算机)中的各种声音硬件,即使你的这个硬件和声音或者其他的常用媒体没有任何关
系。)
还要准备你的商业后续工作:同WHQL合作。至少,这将要求你从Dun和Bradstreet(或者从同等的商业团体取得许可)获得一个“数据广义编码系
统”的号码,并从Verisign(获得)一个数字签名证书。类似这种著作,DUNS编码是免费的,但是Versign认真却不是。运作通过联合公司的所
有工序将需要时间。
尽早留意最终用户将如何安装驱动程序软件。大多数外设零售商宁愿提供一张包含通用安装程序的CD-ROM,因为编写安装(程序)是一个漫长的工程----
需要花费一个资深程序员几个星期的时间。驱动存储网站(现在)非常普遍,并且对安装问题给予了特别关注。
驱动程序可以用两种方式提供统计和管理信息。视窗“设备管理器(WMI)”系统为各种类别的二进制数据提供一种独立的语言(通道)。微软公司已经为特定种
类的硬件建立了标准的WMI类型,你自己的工业群组可能也已经建立了其他的标准----你的驱动程序应该遵守的标准。第十章包含的信息就是如何遵守微软的
标准,但是查明如何适应其他的工业(标准)可能是你公司商业代表的工作。
提供管理信息的第二种方式:系统事件纪录的意义,这种纪录是视窗NT一开始(就有的)部分,它为系统管理员提供一个捷径:了解最近哪些优先级提高了。你的
驱动程序应该报告的是:那些能够引起系统管理员注意的以及具备可操作性的事件。无论如何,你的驱动程序都应该与资深的系统管理员协商,以便决定记录那些事
件,这样将避免乱七八糟的(信息)无一例外的记录到日常纪录。你的驱动程序的可执行文件也很可能包含了消息文档----一种特殊的通晓各种语言的消息,并
且让一个博学的作者创作这个文档是个不错的主意。(我并不是说你的驱动程序不能做这样的事,但是他或者她可能并不是最好的选择。)
另外,对于一个驱动程序,你可能需要控制面板和其他的配置软件。驱动程序设计人员和一位专家--在互相干扰用户(方面)--应该合作构建这些组成部
分。(这些组件)从它们随着安装驱动程序(一起)被安装开始,它们将成为WHQL数字标志包的一部分,因此它们也将随着驱动程序的结束而同时结束。
最后,不必调整驱动程序的无关紧要的细节。一个好的驱动至少具备一个顺利的安装(界面)作为产品的外部表现。简而言之,如果你的驱动使得操作系统崩溃,
(驱动)检查者将提醒公众,并且任何没有读到检查(报告)的人都将愤怒的把你的产品推给商店。由于你的驱动,在那些系统被摧毁的人们当中,你甚至没有任何
一个回头客。所以对于一个资金不够雄厚的驱动开发者,一个短视的决定,就很容易导致一个你的底线上的戏剧性失败结果的很快来临。这个建议对于发展中国家的
硬件制造商尤其重要,(因为)这些(厂家)的管理者趋向于寻找任何可能去降低成本。我建议:应当将驱动开发作为一个基础投资决定。
概括来说,用心的依照以下步骤来计划你的开发方案:
1.评估驱动需要和选择编程人才。
2.编辑
硬件说明文档----完全满足驱动工作开始的需要。
3.供驱动测试的硬件雏形。
4.作为原始构思:驱动程序和硬件/软件硬件接口。
5.
在所有操作系统中测试安装文件。
6.提前配置控制面板和其他所需软件。
7.测试和完成WMI和事件纪录功能。
8.WHQL自检通
过并且满足需要。
9.通俗安装程序完成。(不属于WHQL的需要部分)
10.准备刻录光盘以及随产品附送。
注:加括号的是作者添加的文字,主要是连接上下文以及代词的翻译。
第二章 WDM驱动程序的基本结构
在第一章,我描述了Microsoft Windows XP和 Microsoft Windows 98/Me操作系统的基本架构。解释了驱动程序的目的是管理一块代表系统的硬件,并且还讨论了如何决定你的系统会需要什么样的驱动程序。在这章里,我将更 加具体地描述WDM驱动的程序代码和各种在一起管理硬件的驱动程序的不同。还会引出系统是如何找到并装载驱动的。
2.1驱动如何工作
一
个不错的理解完整的驱动程序的方式是将其想象成是一个收集让操作系统调用的用来执行各种涉及到你硬件操作的子程序的容器。图2-1说明了这一概念。一些程
序,如DriverEntry和AddDevice程序和一些类型的I/O
请求包(IRP)派遣函数会出现在每个这样的容器中。需要队列请求的驱动程序可能还有一个StartIo程序。而执行直接存储器存取(DMA)操作的驱动
程序会有一个AdapterControl程序。产生硬件中断的驱动程序也会有一个中断服务程序(ISR)和一个延期程序调用(DPC)程序。多数的程序
会有除了在图2-1中列出的三个之外的一些IRP种类的派遣函数。因此,若你作为WDM驱动程序的编写者,你则需要选择相应函数到你具体的容器中。
图2-1被看成是子程序包的驱动模型
在本章中,我将给你演示如何编写一个单功能驱动的DriverEntry和AddDevice程序,这类驱动是本书要讨 论的内容之一。在后面的章节中你还会发现filter驱动也有类似的DriverEntry和AddDevice程序,而minidriver则有着很不 同的DriverEntry程序,并完全根据相关类别的驱动程序的作者来设计相应的驱动接口以决定是否采用AddDevice程序。
应用程序如何工作?
和应用于应用程序的“主程序和辅助程序”模型相比,我们值得想一下驱动程序的“子程序包”模型的含义。看看这个程序,这 是我们大多数开始编程序的时候写的:
int main(int argc, char* argv[])
{
printf("Hello, world!");
return 0;
}
这个程序由一个名叫main的主程序和一个多数时候我们不明确调用的辅助程序库组成,如printf,其给一个标准输 出文件输出了一个消息。在编译完包含主程序的原始程序之后,将结果与一个含有printf等主程序所需要的辅助程序运行时库连接在一起,最后以一个可执行 模块告终,譬如HELLO.EXE。其和现在或之后的应用程序大体相同,所以我只要直接通过该应用程序的名字就可以调用它, 可以用如下方法调用这个程序:
C:\>hello
Hello, world!
C:\>
关于应用程序还有一些其他共通点:
n 一些应用程序所使用的辅助程序来自一静态库,连接器将其作为构建过程的一部分。Printf就是这些函数之一。
其他的辅助程序是从系统的动态链接库(DLL)上被动态链接的。对于这些程序,链接器将专用的引入表放入到可执行文件文 件中,并且最终运行时装载器将这些参考地址修正令其指向真正的系统代码。实际上,由应用程序使用的整个Win32 API都是被动态链接的,所以你能看到动态链接在Windows编程中是一个非常重要的概念。
n 可执行文件还能够包含一些可以让调试器和源代码联系在一起的符号信息。
n 可执行文件也可以包含资源数据,如对话框模板,文本串,和版本信息。在文件内部的这种形式的数据比使用单独的辅助文件的效果要好,因为这可以避免存在不匹 配文件的问题。
有意思的是,就如HELLO.EXE这样的程序一旦操作系统把控制权给它。它直到其完全结束完它要执行的任务之后才会返 回。实际上这是你用过的所有Windows应用程序的一个特征。在就像HELLO.EXE这样的控制台模式应用程序下,操作系统最初将控制全转给一个初始 化函数,这个函数是编译器运行时库的一部分。该初始化函数最终调用mian程序来完成应用程序的工作。
Windows的图形化应用程序的工作方式同理,只不过主程序名称用WinMain代替了main。WinMain操作 一个消息队列用来接收和派遣窗口程序的消息。当用户关闭主窗口时其返回操作系统。如果你曾经只用MFC(Micorsoft Foundation Classes)构造过Windows应用程序,你会发现没有WinMain程序。实际上WinMain程序是被隐藏到了一个你绝不会找到的一个库中了, 可以肯定,它是确实存在的。
即使在一台只有一个CPU的计算机上也可以有多个应用程序被同时运行。操作系统的核心含有一个调度程序可以给当前符合条 件的线程运行一小段时间(这被称为时间片)。一个应用程序由一单线程开始并且如果它需要,还创建更多的线程。每个线程有系统给予的优先级优先级,并且根据 不同情况还会被调节,在每次调节时,调度程序会选出优先级最高的符合条件的线程,并把一系列的被保存的寄存器映象,包括指令指示器(IP)装载给处理器的 寄存器,通过这种方式把控制权给它。伴随着线程的时间片失效,处理器中断。作为处理中断的一部分,系统会保存当前寄存器的状态。这样当下次还是这个线程被 执行的时候,它的状态还可以被恢复。
一个线程不止是只等待时间片的过期,它还可以在别的线程里阻塞其耗时的行为,直到这个行为结束。这比轮流等待完成的方式要好,因为若系统不得不靠一个时间 片的过期才能将注意力转移到别的线程上来的话,这样做则允许其他线程运行的比它们预计的要快。
现在你应该明白我所说的了吧。我集中描述了应用程序运行的实质。在底层,一个线程的过程包括获取CPU资源,尝试挂起直到其退出。而操作系统调度程序则像 扮演了一个运动场监管者来让各自的线程在一起很好地比赛。
设备驱动
就像HELLO.EXE程序一样,驱动程序也是可执行文件。其扩展名为.SYS,但在结构上严格地说这文件和任何32位的Windows或者控制台模式的 应用程序没什么区别。和HELLO.EXE程序一样,一个驱动程序也需要许多辅助程序,许多也都是动态链接自操作系统内核或来自一组驱动或者时其他的支持 库。当然,一个驱动程序文件也可以有调试信息和资源数据。
系统在看管下
但是不像HELLO.EXE程序的是,驱动程序没有主程序。取而代之的是其包含了一批系统可以在适当的时候调用的子程序 集。固然,这些子程序还可以在驱动程序中,静态库中,和操作系统中使用辅助子程序,但驱动程序除了其其自己的硬件外没有照管任何别的事情:系统管理了其他 的任何事务,包括决定在何时运行你的驱动程序代码。
如下是一个关于操作系统会如何调用你驱动里的子程序的简要过程:
1. 用户装入了你的设备,这样系统就会装载你的可执行的驱动程序到虚拟内存中并且调用你的DriverEntry程 序。DriveEntry做了一些操作之后返回了。
2. 即插即用管理器(PnP Manager)调用你的AddDevice程序,让其做一些操作之后返回。
3. 即插即用管理器发送给你一些IRP,你的派遣函数依次处理每一个IRP之后返回。
4. 一个应用程序打开了一个你的设备的句柄,因此系统发送给你另一个IRP,你的派遣程序做了少量操作之后返回。
5. 应用程序尝试读取一些数据,因此系统发送给你一个IRP,你的派遣程序将IRP放入一个队列之后返回。
6. 一个之前的I/O操作被一个你驱动连接到的硬件中断结束。你的中断程序做一些很少的操作,制定一个DPC,之后返 回。
7. 你的DPC程序运行了,在其他的事务中,它从第五条中的IRP队列中移除了IRP并安排你的硬件读取数据。之后 DPC程序返回系统。
8. 随着时间变化,在这期间系统会对你的子程序有许多简短地调用
9. 最后,最终用户卸掉了你地设备。即插即用管理器发送给你一些IRP,由你进行处理并返回,操作系统调用你地 DriverUnload程序,完成相当数量的细微操作之后返回。然后系统将你的驱动程序代码移出虚拟内存。
在过程中的每一步,由系统来决定你的驱动需要做哪些事情,将其初始化,处理IRP,处理中断,或者什么。因而系统在你的 驱动程序中挑选适当的子程序。之后你的子程序会做相应的操作之后返回系统。
线程和驱动程序代码
驱动程序和应用程序的另外一个不同是运行驱动程序时系统并不创建专门的线程。取而代之的则是当某一个线程被激活的时候由 系统来决定是否执行驱动程序里的子程序
当一个硬件中断发生的时候我们不可能预测到此时正是哪个线程。同样,想象一下你在游乐园正在观察旋转木马。其上的木马就类似于系统中的线程,我们将离你最 近的木马称为当前线程。现在我们假设当你无意中听到有人说“That’s awesome, dude.”时你决定用你的相机拍照(在我游乐园的经验中,这不会有长时间的等待),你不能预测到在你的快照中哪只马是当前的。当硬件发生中断的时候哪个 线程正在执行中也是不可预测的。我们称其为任意线程,我们所说的都是一个任意线程。
当系统决定调用你驱动程序中的子程序时,它常常运行着一个任意线程上下文。线程的上下文会是任意的——例如,当你的中断服务程序获得控制权。如果你制定一 个DPC,那么运行你DPC程序的那个线程将会是任意的。如果你排队等待IRP,你的StartIo程序会被一个任意线程调用。事实上,如果一些在你的堆 栈外驱动程序发送给你一个IRP,你也不得不假设线程上下文是任意的。这通常会是给存储驱动程序的场合,因为一个文件系统驱动程序会成为对读写负责的代 理。
系统不总是在任意线程上下文中执行驱动程序代码。一个驱动程序可以通过调用PsCreateSystemThread函数来创建自己的系统线程。驱动程序 也可以让系统在有相关操作项目的系统线程中回调它。在这些情况下,我们认为线程上下文不是任意的。我将在第14章讨论系统线程的结构和工作项目。
不是任意的进程上下文的另一种情况发生在当应用程序放出一个了引起I/O管理器直接给驱动程序发送IRP的API调用时。你能知道你处理的每一个IRP是 否和你写的驱动有关
有两个理由让你关心进程上下文是否是任意的。首先,驱动程序不应该阻塞任意线程;虽然你激活了一些其他的线程,但是这不 公平。
第二个理由是当驱动程序创建了一IRP,并将其发送给一些其它的驱动程序。我会在第五章充分讨论,虽然你需要在一任意线 程中创建一种IRP(异步IRP),但你可能却在非任意的线程内创建了不同种类的IRP(同步IRP)。I/O管理器将同步种类的IRP连接到你创建 IRP的线程上。若该线程结束了其也会自动地删去IRP。但I/O管理器不将任何异步IRP连接到任何线程上,你创建的一个异步线程可能不会执行你要做的 任何I/O操作。并且由于线程的偶然终止还会导致系统取消IRP错误。所以其不行。
均衡多处理技术
Windows XP使用了一种所谓的均衡模型来管理多处理器的计算机在这种模型下,每一个CPU像所有其他涉及线程调度地CPU一样被严格地处理。每个CPU豆科一拥有 其自己的线程。这可能对I/O管理器,在两个或更多的CPU上执行线程的上下文,同时调用你驱动中的子程序来说是完美的,我没有说那种运行在单CPU上的 线程的那种假同时,这些线程只能同时被运行一个,并根据时间来轮换。而一个多处理的机器不同的线程则可真正地被同时执行。你可以想象,这种同时执行使驱动 对共享数据的同步访问有着严格的要求。在第四章里我将会讨论你可以用于此目的的不同的同步方法。
2.2系统如何找到并装载驱动
我在前面的部分强调了操作系统是如何全面负责计算机并访问设备驱动程序来做一些有关硬件的操作。驱动程序在使其
被首先加载的处理中扮演一个同样被动的角色。如果你很快就能明白系统是如何发现硬件,确定装载哪些驱动程序,和配置驱动程序来管理硬件的话,那么这将有助
于你了解本书的其他内容。系统使用了两种稍微不同的方法,这依赖于硬件是否具有即插即用的兼容性。
n 即插即用设备有一个系统可以发现的电子签名。对于即插即用设备,系统总线驱动程序将发现其是否存在,并且读取这个签名来判定它是哪种硬件。随后一个基于注 册表和INF文件的自动处理保证了系统可以正确地加载驱动。
n 一个非即插即用的设备则没有这个电子签名,所以系统不能自动地发现它。最终用户因此必须通过调用添加新硬件向导来启动“探测”处理,最终系统发现了新硬件 的存在。其后系统用和即插即用同样的方法自动处理注册表和INF文件保证了系统可以正确地加载驱动。
无论系统使用哪一种方法来发现硬件并装载驱动,该驱动程序自身都将会成为一个被操作系统被动调用的WDM驱动。在这一点 上,WDM驱动与Windows NT早期版本的内核模式驱动和Windows 95之前的VxD驱动形成了鲜明的对比。在那些环境里,你必须用某些方法来让系统装载你的驱动。该驱动之后会扫描硬件总线来查找它自己的硬件并决定是否常 驻内存。另外,你的驱动程序还不得不得确定使用那些I/O资源,并设法阻止其他驱动占有同样的资源。
设备和驱动程序的层次结构
在搞清发现硬件和驱动程序装载的过程之前,我需要说明图2-2中驱动程序的层次结构的概念。如图2-2所示,左列描述了 一个向上链接的使用内核DEVICE_OBJECT结构的栈。涉及到系统是如何管理一块单一硬件的。中间的这列显示了扮演管理角色的设备驱动集。右边那一 列阐述了一个IRP通过驱动程序的流程。
图2-2 WDM中的设备对象和驱动程序的层次结构
在WDM中,每一个硬件设备至少有两个设备驱动。其中一个我们称之为功能驱动,这是你最关心的设备驱动。它了解使硬件如 何工作的所有细节。它负责初始化I/O操作,处理发生在这些操作完成时的中断。并且给最终用户提供一个方法来适当的演练控制硬件。
注:
一个单独的WDM功能驱动是一个动态链接到包含操作系统内核和的HAL.DLL(其包含有系统抽象层(HAL))的 NTOSSKRNL.EXE的单一可执行文件,一个功能驱动也可以动态链接到其他内核模式的DLL。微软已经给你的硬件类型提供给了类驱动程序,在这种情 况下,你的迷你驱动(minidriver)会动态链接到类驱动程序上。迷你类驱动和这类驱动联合组成了功能驱动程序。你会看到调用类驱动于小类驱动之上 之下的类似的事情。我更关心这些所谓的“类”驱动作为独立的过滤器驱动并对相关的迷你驱动的驱动单独地使用规则类驱动,通过明确的外在的引入,使迷你驱动 自动地发挥。
我们调用另一个驱动是每个设备都要有的总线驱动。其负责管理硬件与计算机的连接。例如对于周边元件扩展接口
(PCI)总线的总线驱动的就是用来探测你的板卡是否被插入到了PCI槽上并来确定你的板卡对于I/O映像或存储映像的需求的软件组件。其还是开关电流到
你板卡上的流程的软件。
一些设备有两个以上的驱动程序。我们用一般规则过滤器驱动来描绘这些驱动。一些过滤器驱动简单地监视作为功能驱动完成IO任务。经常一个软件或硬件供应商 会提供过滤器驱动来在某些方面修改存在的功能驱动程序的行为。上层过滤器驱动程序在功能驱动之前获得IRP,并且其有机会支持驱动程序不了解的额外的方 面。有时上层过滤器驱动能够针对在功能驱动或硬件上的Bug来执行一工作区。而下层驱动程序获得的是功能驱动程序尝试发送给总线驱动前的IRP 。(一个下层过滤器驱动在栈里位于功能驱动之下而在总线驱动之上)。在一些情况下,如当设备是附加于通用串行总线(USB)的,下层过滤器是可以修改功能 驱动程序试图执行的总线操作流的。
我们再看一下图2-2,注意这四个驱动程序中的每一个都被显示为一假定的连接到左侧DEVICE_OBJECT结构的设备。这些结构中的缩写是:
n PDO代表物理设备对象(Physical Device Object)。总线驱动程序用这一对象来描述设备和总线之间的连接。
n FDO代表功能设备对象(Function Device Object)。功能驱动程序用这一对象来管理设备的功能。
n FiDO代表过滤器设备对象。过滤器驱动程序使用这一对象作为存储其需要保存的关于硬件和其的过滤行为的地方。(早先释放的Beta版的Windows 2000 DDK用了规则FiDO,当时我采纳了它。DDK不在使用这规则,据我猜测,它被认为太轻佻了。)
什么是总线?
我恰当地使用了规则总线和总线驱动程序但没有说明其是什么意思。对于WDM的目的,总线是你可以插入设备的任何东西,而 不是物理上或者比喻上的。
我想这是一个恰当的而广泛的定义。其不止包括如PCI总线,还包括小型机算计系统接口(SCSI)适配器,并行端口,串 行端口,USB集线器等等的任何东西。事实上只要他能让另一个设备插上去。
这一定义还包括了一我虚构出来的根总线。用来关心所有非即插即用的设备。这样,根总线就是非即插即用工业标准结构 (ISA)板卡或连接到串行端口但不使用标准的辨认串来答复串行端口列举信号的智能卡读卡机的双亲。我们也可以认为根总线是PCI总线的双亲——这是因为 PCI总线并不自我告知其物理存在,因此操作系统只能将其识别为非即插即用设备。
即插即用设备
再重复一次我之前所说,即插即用设备是一种拥有能使总线获悉设备身份的电子签名的设备。如下是这些签名的一些例子:
n PCI卡有一PCI总线驱动可以通过专注内存或者I/O扩口地址读到的构形空间。该构形空间包括了供应商和产品识别信息。
n USB设备返回一设备描述符来响应一标准控制管道处理。该设备描述符包括了供应商和产品识别信息。
n 个人计算机存储卡国际协会(Personal Computer Memory Card International Association,PCMCIA)设备有一PCMCIA总线驱动程序可以读取的特征内存,根据这可以来识别该卡的身份。
对于即插即用总线的总线驱动程序有能力通过在启动时扫描所有可能的插槽来列举其总线。对于热插拔设备(如USB和 PCMCIA)总线的驱动,它们还要监测是否还有新的设备到来的硬件信号,因此驱动程序还要再次列举其总线。最后的结果就是列举或者再列举处理成为了一 PDO集。如图2-3①。
图2-3 安装即插即用设备
当总线驱动程序检测到硬件的增删,其会调用IoInvalidateDeviceRelations来告知给即插即用管理器(PnP Manager)总线上孩子设备的数量已经改变。即插即用管理器发送一IRP给总线驱动来获得关于孩子设备的最新PDO。这个IRP的主功能代码是 IRP_M3_PNP,而辅功能代码是IRP_MN_QUERY_DEVICE_RELATIONS,用代码表明即插即用管理器寻找所谓的“总线”联系。 图2-3中②表明了这一点。
注:
每一个IRP都有主/辅功能代码。主功能代码指出IRP包含什么类型的请求。IRP_M3_PNP是适用于即插即用管理 器生成的请求的主功能代码。就主功能代码而言(包括IRP_M3_PNP),辅功能代码被用来说明更详细的操作。
为相应总线关系查询,该总线驱动程序返回其PDO列表。即插即用管理器可以轻松地确定哪些PDO代表它未初始化的设备。 让我们集中注意力在你硬件的PDO上,看看接下来会发生什么。
即插即用管理器会发送另一个IRP给总线驱动,这次是辅功能代码IRP_MN_QUERY_ID。如图2-3③。事实上 即插即用管理器会发送几个这样的IRP,每一个都有一令总线驱动返回一标识符类型的操作数。其中一个操作数的设备标识符唯一指定了设备的类型。设备标识符 是像如下例子中的串:
PCI\VEN_102C&DEV_00E0&SUBSYS_00000000
USB\VID_0547&PID_2125&REV_0002
PCMCIA\MEGAHERTZ-CC10BT/2-BF05
注:
每个总线驱动程序有其自己的收集入标识符串的用于格式化电子信号信息的架构。我将会在第15章讨论被用于通用总线驱动的 标识符串。那章也是查找关于INF文件和被以文本模式描述在注册表层次中的注册表键值并在注册表中保存为何类信息的地方。
即插即用管理器用硬件标识符来定位在注册表中的硬件键值。目前,让我们假设你的驱动是第一次被插入到计算机中。如果是那样的话,还不会有对于你的硬件的类 型的键值。此时安装子系统会帮助找到哪些软件适合你的驱动。(请看图2-3④)
对于所有硬件类型的安装指令存在于扩展名为.INF的文件中。在每一个INF文件中有一个或多个样式声明包含相关的用于 安装的详细设备标识串的节。对于一个崭新的硬件,安装子系统会试图从相关的INF文件中找到何硬件标识符匹配的样式声明。提供这些INF文件就是你的责任 了,这就是为什么我在图中将这一步指向你。我在指出系统是如何搜索INF文件和将这些其很可能找的样式声明说的含糊一些。我将在第15章具体说明,现在讨 论这些还是有点深的。
当安装子程序找到了正确的样式声明,其会执行你 在安装节中提供的指令。这些指令可能会包括在最终用户的硬盘上复制一些文件,在注册表中定义的一个新的驱动服务等等 。在处理的最后安装程序会在注册表中创建硬件键值并安装你提供的所有的软件。
现在回到上面几段,假如现在不是第一次在相关电脑上安装你的硬件。例如我们谈论的USB设备已经被用户移除,现在用户要 将其再插入到系统上。如果是这样的话,即插即用管理器会找到相应的硬件键值,并不需要调用安装程序。因此即插即用管理器将跳过安装处理直接到图2-3⑤。
这里,即插即用管理器能认出存在你的驱动程序负责的设备。若你的驱动程序还没有被载入虚拟内存,即插即用管理器会调用内 存管理器来映像之。系统没有将你驱动器上的磁盘文件直接读取入内存。取而代之的是其创建了可令驱动程序代码被分页I/O获取的文件映像。除了其有当你允许 你的驱动被清出映像时的令你小心的后遗症外,实际上系统使用了文件映像并不会影响你太多。这样内存管理器之后会调用你的DriveEntry程序。
接下来即插即用管理器调用AddDevie程序会告知你的驱动程序有一新的设备被发现。(如图2-3⑤)。其后即插即用 管理器会发送一辅功能代码为IRP_MN_QUERY_RESOURCE_REQUIREMENTS的IRP给总线驱动。该IRP主要是请求总线驱动来描 述你的设备对于中断请求线, I/O端口地址, I/O内存地址,和系统DMA通道的需求。该总线驱动会构建这些资源需求的列表并将其传达。(如图2-3⑥)
最终,即插即用管理器即将配置硬件。其会令一资源分配器给你的设备分配资源。如果这些都做完了,即插即用管理器就会用辅 功能代码IRP_MN_START_DEVICE发送IRP_MJ_PNP给你的驱动。你的驱动会通过配置和连接不同的内核资源来处理该IRP,之后你的 硬件即将使用。
Windows NT 驱动程序对比
文中描述了Windows XP(当然还有Windows 2000,Windows 95,核Windows 95的所有后续版本)是如何查找并要求驱动是相对被动的装载的过程。但Windows NT 4.0以及之前的版本却处理的很不同。在这些系统中,你已经提供了某些用来安装你驱动的安装程序。你的安装程序会更改注册表来保证其在下一次重启德时候会 被装载。这样,系统会装子你的驱动程序并调用DriveEntry程序。
你的DriveEntry程序会以某种方法来检测你的那些硬件实例是真正地存在。你可能会扫描所有PCI总线的插槽,例 如,还是假定你设备的每一个实例符合注册表中的子串。
发现完你的硬件之后,你的DriveEntry程序会继续指派和存储I/O资源并做现在WDM驱动程序做的配置和连接的
步骤。如你所见,因此WDM驱动程序比之前版本的Windows NT的驱动程序有更少的启动工作。
非加插即用设备
我用术语 legacydevice来描述任何不支持即插即用的设备,这意味着系统不能自动检测到其的存在。让我们假设你的设备在这其中。用户买到你的设备后,其首 先会调用添加新硬件向导之后会做一系列的对话框选择来将安装程序引导到一INF文件中的安装节。(如图2-1①)
图2-4 一非即插即用设备的发现过程。
安装程序根据在安装节中的命令用列举器创建注册表入口(如图2-1②)。该注册表入口可能包括一逻辑配置,其列出了设备 I/O资源请求(如图2-1③)。
最终,安装程序会通知最终用户重新启动系统(如图2-1④)。安装系统的设计者希望最终用户此时应按照厂商的使用说明通 过设定跳线或开关来配置你的板卡,并希望在系统关机的情况下将其插入扩充插槽。
在重启动之后(或按最终用户的要求绕开充启),根列举器会扫描注册表,并找到新添加的设备。其后,装载你的驱动的过程和 即插即用设备的一样。如图2-5
注:
大多数的驱动程序的例子对应的内容都是虚假的硬件,你将其作为非即插即用设备(不存在的)硬件来安装。例子中的一两个对 I/O端口和中断起作用。各自的INF文件包含有记录配置的节使即插即用管理器分配这些资源。如果你通过添加新硬件向导来安装这些驱动中的一个,系统会认 为这需要一个断电重启,但实际上你并不需要充启。
图2-5 装载一非即插即用驱动程序。
递归列举
在之前的部分中,我描述了对于一单独的设备,系统是如何装载正确的驱动的。这一描述提出了一个问题:系统是如何在计算机中设法装载所有硬件的驱动的?答案 是:其使用递归列举。
在第一个例子中,即插即用管理器调用根列举器来查找其不能电子地宣布其存在的所有硬件——包括主硬件总线(如PCI)。 根总线驱动程序从注册表中获取被Windows XP安装程序初始化了的计算机信息。安装通过运行一详细的硬件检测程序和向用户询问适当的问题来获取信息。因此,根总线驱动程序就有了足够的信息来给主总 线创建PDO。
主总线的功能驱动之后可以电子地列举其自己的硬件。当总线驱动列举硬件时,其扮演为一普通的功能驱动。然而在发现新硬件 后,该驱动程序则转变了角色:它成为了总线驱动,并给新发现的硬件创建新的PDO。即插即用管理器之后就和以前讨论过的一样为这个设备PDO装载驱动程 序。对于设备的功能驱动程序还可能列举出更多的硬件,这种情况下,整个过程是重复递归的。最终的结果会像是图2-6中所示的树结构:其中,一总线设备栈分 支到附加到该总线上的硬件的其他设备栈。图中深色框代表一个驱动程序可以戴多少顶“FDO帽子”来扮演对于其硬件的功能驱动,多少顶“PDO帽子”来扮演 附加设备的总线驱动。
图2-6递归列举设备层次
驱动的装载顺序
我一开始说过设备可以拥有上层和下层过滤器驱动和功能驱动程序。有两个和设备关联的注册表键值包含了过滤器驱动的信息。 包含有你硬件实例的信息的device键可以有给该实例指定过滤器驱动的UpperFilters和LowerFilters值。另一个注册表键值记录了 设备是属于什么类(class键)的。例如,鼠标属于鼠标类,这样不用我说你就应该明白了吧?class键也可以包含UpperFilters和 LowerFilters值。其指定了系统会为每一个属于该类的设备装载的过滤器驱动。
不论其在哪儿出现,UpperFilters和LowerFilters值的类型都是REG_MULTI_SZ的,并且 还可以因此包含一个或者更多的以空结尾的Unicode字符串值。
注:
Windows 98/Me不支持REG_MULTI_SZ注册表类型并且也不完全支持Unicode。在Windows 98/Me中,UpperFilters和LowerFilters的值是REG_BINARY类型的,该类型可包含多个以空结尾的ANSI字符串,并最 后以一额外的空结束符为结束。
了解系统调用驱动的顺序在某些时候可能是很重要的。装载驱动的实际过程必须要让驱动代码映像到虚拟内存中,这个顺序并不值得关注。但你应该关注在不同的驱 动程序中调用AddDevice函数的顺序。(请参照图2-7)
图2-7调用AddDevice的顺序。
1. 系统首先调用被列在device键中的下层过滤器驱动中的AddDevice函数。其按LowerFilters值的顺序出现。
2. 其次系统调用被列在class键中的下层过滤器驱动的AddDevice函数。其顺序为驱动出现在LowerFilters串中的顺序。
3. 之后系统调用被列在在device键中Service值的驱动程序的AddDevice函数。这是功能驱动。
4. 系统调用被列在device键中的上层过滤器驱动中的AddDevice函数。其按UpperFilters数据串中的顺序出现。
5. 最终系统调用被列在class键中的上层过滤器驱动的AddDevice函数。其顺序为驱动出现在UpperFilters数据串中的顺序。
就像我在本章说的,每一个AddDevice函数创建了一个DEVICE_OBJECT内核并将其链接到以PDO为根 的栈中。因此,调用AddDevice的顺序决定了栈中设备对象的顺序 ,并且最终驱动程序看到了IRP的顺序。(the order in which drivers see IRPs.)
注:
你也许已经注意到了属于类和不作为你所期望的纯嵌套的设备实例的上/下层过滤器的装载问题了。在我知道事实之前,我猜设 备级别的过滤器可能比类级别的过滤器更接近于功能驱动程序。
IRP路由
在WDM中驱动的正式分层使IRP以可预言的方式从一个驱动向另一个驱动的传递变得很容易。图2-2阐述了大体的思路: 只要系统打算在一个设备上实现一个操作,其会给在栈中最高的过滤器驱动发送IRP。驱动程序可以决定是处理该IRP,还是将IRP传递给下一级别,或是两 者皆做。每一个留意IRP的驱动都进行同样的动作。最后IRP可能会在其的PDO角色中到达总线驱动。尽管图2-6似乎还在暗示什么,但等其到了总线驱动 一般就不会再传递IRP了。相反总线驱动通常结束该IRP。在一些情况下,总线驱动会将同样的IRP传递给扮演FDO角色的栈(双亲驱动栈)。在其他情况 下,总线驱动程序会创建一二级IRP并将其传递给双亲驱动栈。
设备栈是如何被实现的
我会在本章稍后的地方说明DEVICE_OBJECT数据结构。不透明的字段AttachedDevice将设备对象链 接到栈顶。以PDO为开始,每一个设备对象立刻指向了其上的对象。不能证明向下的指针——……(事实 上,IoAttachDeviceToDeviceStack在一DDK没有全部生命的结构中建立了一向下的指针。对于逆向工程来说这是不明智的因为其会 再任何时候被改变。)
AttachedDevice字段故意没有被证明是因为其适当的使用需要同步的可能会将设备从内存中删除的代码。我们被 允许通过调用IoGetAttachedDeviceReference来找到栈中顶端的设备对象。该函数还会添加一参考计数来防止对象被过早的移出内 存。如果你打算使你下至PDO的方式运转,你应该以辅功能代码IRP_MN_QUERY_DEVICE_RELATIONS和类型参数 TargetDeviceRelation发送给你一IRP_MJ_PNP请求。PDO驱动会通过返回PDO的地址来响应。当你第一次创建设备驱动时,由 于只需要记住PDO地址,这会很简单。
类似的,若想立即获悉哪个设备对象在你之下,你需要保存你第一次添加你的对象到栈中的地址。既然在栈中的每一个驱动程序
会有其自己未知的用IRP派遣来实现向下指针的方式,那么这样,一旦栈被创建,就不能被更改了。
一些例子会阐明FiDO、FDO和PDO之间的关系。第一个例子涉及到在通过PCI到PCI桥接芯片将自己放在主总线上 的二级PCI总线上的设备的读取操作。简单起见,让我们假设存在一个适合于此设备的FiDO,如图2-8所述。在之后的章节中你会学到一个读请求通过以主 功能代码IRP_MJ_READ变成IRP。这样的请求会首先流动到上层FiDO之后流到设备的功能驱动。(该驱动就是被标记在图中FDOdev中的设备 对象的驱动。)功能驱动程序直接调用HAL来执行其功能,所以图中没有其他驱动程序能够注意到IRP
图2-8 对于在二级总线上设备的读请求流程。
修改一下第一个例子,其结果显示在图2-9中。现在对于插到一个其本身被插到主机上的USB HUB上的设备,我们有一个读请求。对于Hub和主机来说,全部的设备树因此包含设备栈。IRP_MJ_READ穿过FiDO到了功能驱动,功能驱动之后 发送一个或更多不同种类的向下到其自己PDO的IRP。适用于USB设备的PDO驱动是USBHUB.SYS,并且其将IRP运送到主机设备栈中的顶端的 驱动,跳过了USB hub的两个驱动栈。如图中部所示。
图2-9 对于USB设备的读请求流程。
第三个例子除了正被讨论的IRP是一个关于是否PCI总线上的磁盘驱动器会作为系统分页文件的存储地点的通知以外,其他 的都和第一个例子类似。你会在第六章学到该通知用辅功能代码IRP_MN_DEVICE_USAGE_NOTIFICATION表现为一 IRP_MJ_PHP请求的形式。此时FiDO驱动程序传递请求给注意到它的并会将其传递给栈中更下方的PDOdev驱动的FDOdev驱动。此特殊的通 知还引出了其他关于即插即用系统或电源管理的I/O请求是如何被处理的。所以PDOdev驱动给在FDO总线内部的栈发送同一通知。如图2-10。(并非 所有的总线驱动都以这种方式工作,但PCI总线确是)
图2-10 设备用法通知的流程
形象化设备树
更好的形象化设备对象和驱动程序的方式是分层,在这里工具是有助于你的。我写了DEVVIEW工具,在附加内容中你会找 到它。用第十二章中插入到二级USB Hub上的USB42的例子来说,我运行了DEVVIEW并在截了两个图(如图2-11和2-12)。
此设备只用了两个设备对象。PDO被USBHUB.SYS管理,而FDO被USB42管理。在第一张截图中,你可以看到 关于PDO的其他信息。
值得在你自己的系统上试验一下DEVVIEW来看看你自己的硬件有多少不同的驱动被分层了。
2.3两个基本的数据结构
这部分(内容)(主要)讨论两个最基本的有关WMD驱动程序的数据结构:驱动实体和硬件实体。驱动实体指的是驱动(程序)自身以及所有驱动子程序指针 ----系统将在运行时调用这些子程序。归根结底,你应该明确:你在自己的驱动程序中经常提供的指向日常(命令)的指针,(在系统中)是作为各种内核服务 请求中的中断(命令)的。硬件实体指的是一个硬件实例及其包含的有关帮助用户操作该硬件的数据。
驱动实体
I/O管理使用一个驱动实体数据结构来表征每一个硬件实体。(参看2-13。)类似我们讨论的许多数据结构一样,驱动实体是半透明的。这就是说,你和我有
可能直接访问或者修改的仅仅是数据机构中的特定文件,即使DDK的头文件声明了全部的(数据)结构。我曾经展示过灰色背景数字中驱动实体的半透明文件。这
些半透明文件类似于C++类中的私有成员和受保护成员,同样允许访问的文件类似于公有成员。
DDK的头文件按照一种模式方法(来)声明驱动实体,并且所有其他核心模式数据结构(也)针对这个问题,如同如下从WDM.H(文件)中摘录(部分)说
明:
typedef struct _DRIVER_OBJECT {
//定义一个驱动实体的结构体
CSHORT
Type; //类型
CSHORT
Size; //大小
} DRIVER_OBJECT, *PDRIVER_OBJECT; //成员:驱动实体,驱动实体指针
这段代码的含义:开头声明一个类型名为:DRIVER_OBJECT的结构体。它同时还声明了一个指针类型(PDRIVER_OBJET)并且分配
了一个结构标签(_DRIBER_OBJECT),这种申明类型出现在DDK中的很多地方,因此我以后将不再提起。头文件还声明了一个类型名的小设置(比
如CSHORT)以便描述在内核模式中使用的原子数据类型。举个例子,CSHORT的意思是:带符号的短整型(数据)作为基本数据来使用。
表
2-1列出了一些这样的名称:
类型 大小
驱动实体
标志位
驱动程序开始
驱动大小
驱动程序段
驱动程序扩展部分
驱动名
硬件数据库
最快I/O调度
驱动内核
驱动起始I/O
驱动卸载
主要函数
数字2-13.DRIVER_OBJECT数据结构。
表2-1.常用内核驱动的类型名
类型名 含义
PVOID,PVOID64 类指针(缺省精度和64位精度)
NTAPI
用于服务函数声明强迫使用_STDCALL调用i86系统下的习惯用法
VOID
等价于“void”,无需返回的。
CHAR,PCHAR
8位字符,相同的8位字符指针(标识或者改变默认编程器的值)
UCHAR, PUCHAR
无符号的8位字符,相同的8位字符指针
SCHAR, PSCHAR 有符号的8位字符和指针
SHORT,
PSHORT 有符号的16位整型数和指针
CSHORT 有符号的短整型,用于常用数值
USHORT,
PUSHORT 无符号的16位整型数值和指针
LONG, PLONG
有符号的32位整型数值和指针
ULONG, PULONG 无符号的32位整型数值和指针
WCHAR,
PWSTR, PWCHAR 不定长(独立代码)字符或者字符串
PCWSTR 定长字符串指针
NTSTATUS
身分代码(类型类似有符号长整型)
LARGE_INTEGER 有符号64位整型
ULARGE_INTEGER
无符号64位整型
PSZ, PCSZ 指针:ASCIIZ(单位)字符串或者定长字符串
BOOLEAN,
PBOOLEAN 是或非(等价于UCHAR)
说明:
有关64位类型的说明:DDk头文件包括类型名是为了驱动程序作者能够较方便的在Intel平台上编辑相同的源代码----无论32
位的还是64位的。举个例子来说,不妨假设一个长整型的数值和一个指针具有相同的字长,你需要声明的变量肯能既不是一个LONG_PRO也不是一个
ULONG_PTR。像这样的变量可以是一个长(或者无符号长)(整型)或者一个指针什么的。同样的,例如,使用SIZE_T类型来声明一个整型数据可以
具有同指针相同跨度的字长----你可以在64位平台上得到一个64位的整型数据。在DDK头文件中类似的和其他的32或64位定义类型被命名为
BASETSD.H文件。
现在我将简述驱动实体中可以使用的文件。
DeviceObject(PDEVICE_OBJECT)是位于硬件实体列表中的数据结构,任何一个硬件都是由驱动程序来配置的。I/O管理将驱 动实体及其维护文件联结成一个整体。一个非WDM驱动程序的DriverUnload(驱动卸载)函数将用来作为切断驱动实体和(硬件实体)列表联系的文 件,以便(从列表中)删除他们。一个WDM驱动程序很可能没有任何特别的需要来使用这些文件。
DriverExtension(PDRIVER_EXTENSION)(驱动扩展部分)指向一个微小的精简结构,它包含在那些仅仅添加硬件成员可 以使用的部分。(参看2-14)添加硬件是一个指针:指向在驱动中创建硬件实体的函数;这个函数具有相当大的分量,在本章之后的一段“添加硬件”段落我将 用较大篇幅谈论这个部分。
驱动实体
添加硬件
字长
服务关键名
2-14节.驱动扩展数据结构
硬件数据库(PUNICODE_STRING)作为一个字符串命名为一个“对于硬件的硬件数据库注册表关键字”。这是一个类似于:注册表,机器,硬盘,界
面,系统;而且在注册表关键字中包含了资源分配信息属性。WDM驱动程序无需通过这种关键字中信息的(确认),因为PnP管理自行执行资源分配。名字存储
在独立的代码中。(事实上,所有的内核字符串数据都是用独立代码。)在下一章,我将讨论UNICODE_STRING独立代码字符串树蕨结构的格式和使用
(情况)。
最快I/O管理(PFAST_IO_DISPATCH)指向一组函数指针----文件系统和网络驱动出口。这些函数如何使用已经超出了这本书(讨论的)范
围。如果你有兴趣学习更多有关文件系统驱动的内容,请查阅Rajeev
Nagar的视窗NT文件系统探秘----一个开发指南(O'Reilly及其同事著,1997)。
驱动起始I/O(PDRIVER_STARTIO)指向你的驱动中的一个函数:I/O进程请求----I/O管理已经分配给你的(资源)。在第5章我将大
概讨论有关请求队列以及这种特例中的作用。
驱动卸载(PDRIVER_UNLOAD)指向驱动程序中的清除函数。我将讨论这个函数与驱动载入之间的联系的一些联系,但是你可能也知道现在一个WDM 驱动程序很可能无论如何也不需要任何有意义的清除。
主函数(PDRIVER_DISPATCH队列)是一组驱动中的指针函数:涉及大概将近20多种I/O请求的每一种。如你所料,这组(指针)也占有很重的 分量,因为它定义了在你的代码中I/O请求是如何实现的。
硬件实体
2-15节说明了硬件实体的格式以及在隐含文件中的隐藏条件的使用,这些问题,我在先前驱动实体的讨论中曾经用过。作为一个WDM驱动的作者,你将通过调
用IOCreateDevice(I/O创建实体)来创建很多类似的实体。
驱动实体(PDRIVER_OBJECT)指向的实体联合硬件实体,通常是被一个称为I/O创建实体的创建的。
下个实体(PDEVICE_OBJECT)指向的下一个实体是一个属于相同的驱动程序并且类似这个(实体)的实体。这个文件是将所有硬件实体从一开始就以
驱动实体的“驱动实体成员”联系在一起的。很可能没有原因让一个WDM驱动程序来使用这个文件。或许仅仅因为使用这个指针(可以)要求同步使用一个系统内
部的锁----这个锁并不暴露在外以供硬件驱动程序访问。
类型 大小
参考文件
驱动实体
下一硬件
相关硬件
通用中断请求
时钟信号
标志位
特性
硬件扩展
硬件类型
栈长
请求队列
2-15节.硬件实体数据结构
通用中断请求(PIRP)是微软中断请求队列中“起始包”和“起始后继包”常常用来记录最近发送给你的起始I/O内容。WDM驱动应该遵循她自己的IRP
队列(参看第5章)并且无须使用这些文件。
Flags(ULONG)是一个FLAG片断bit的收集。表2-2列出了这种驱动作者可以使用的bit。
表2-2.在驱动实体数据结构中的Flags。
Flag 注解
DO_BUFFERED_IO
使用缓冲区读写(系统复制缓冲区)来访问用户模式的数据。
DO_EXCLUSIVE
同一时间只允许单线程打开一端。
DO_DIRECT_IO
使用语言模式读写(存储器描述列表)来访问用户模式的数据。
DO_DEVICE_INITIALIZING
硬件实体未初始化完成。
DO_POWER_PAGABLE
IRP_MJ_PNP必须在PASSIVE_LEVEL中启动。
DO_POWEN_INRUSH
在电源启动(过程)中,大量的请求涌入。
特性(ULONG)是另一个标志位的集合,定义成各种各样可选择的设备特性。(参看表2-3)I/O管理根据IOCreateDevice的争端来初始化 这些标志位。过滤器驱动把其中的一些标志位向设备栈的上方传播。
(参看16章内容:有关过滤器驱动的详细讨论以及更多的有关标志(位)上传的信息。)
表2-3.DEVICE_OBJECT数据结构中的特性标志。
标志位 相关说明
FILE_REMOVABLE_MEDIA
媒体可以从设备中卸载。
FILE_READ_ONLY_DEVICE 媒体只可读,不可写。
FILE_FLOPPY_DISKETTE
设备是一个软盘驱动。
FILE_WRITE_ONCE_MEDIA 媒体可以被写一次。
FILE_REMOTE_DEVICE
通过网络连接设备可以被使用。
FILE_DEVICE_IS_MOUNTED 设备中的物理媒体可以访问。
FILE_VIRTUAL_VOLUME
实体音频设备。(该设备物理上存在)
FILE_AUTOGENERATED_DEVICE_NAME
I/O管理可以为一个设备自动生成一个名称。
FILE_DEVICE_SECURE_OPEN
打开是强迫(执行)安全性检查。
设备扩展(PVOID)指向的数据结构:对于设备你定义的将出现在每个实例信息中的部分。I/O管理为这个结构分配空间,但是他的名称及内容完全取 决于你。一个常用的方法是声明一个类型名为DEVICE_EXTENSION的结构体。为了访问给定的设备指针(例如:FDO),使用如下的声明:
PDEVICE_EXTENSION pdx =
(PDEVICE_EXTENSION)
fdo->DeviceExtension;
它很快将成为现实(现在或者什么时候):紧随着设备实体,设备扩展部分即刻出现在内存中。但是依赖于这种通常出现的情况也可能不是个好主意:尤其是 当记录方法紧随DeviceExtension指针而持续工作的时候.
设备种类(DEVICE_TYPE)是一个标示:通常用来表明这个设备是什么类型的.I/O管理基于I/OCreateDevice(提供的)争端 来初始化这个成员.过滤器驱动程序可能确实需要检查这项内容。在这次写操作的时候,对这个成员有超过50种可能的评估。来确定DDK文件是否可以进入列表 中的MSDN内容的“硬件类型详细清单”。
栈长(CCHAR)包含的数目是:硬件实体从开始第一个递减到最后一个PDO的(总数)。这个文件的目的是要确定:首先对这个设备的驱动程序来说, 为一个IRP(中断请求)分配多少(内存单元),以便创建相关部分的栈内容。但是WDM驱动程序往往不需要修改这些值,因为底层程序自动生成这些设备栈 (I/OAttachDeviceToDcviceStack)。
请求队列(ULONG)详细列出了这个设备在读写请求时使用的数据缓存必要的队列(长度/数目)。WDM.H包含了一组常量:将 FILE_BYTE_ALIGNMENT和FILE_WORD_ALIGNMENT修改的等同于FILE_512_BYTE_ALIGNMENT的 值。(将字节、字的长度改为512个字节的代码)值仅仅转换成2减1。举个例子:代码0x3f是一个FILE_64_BYTE_ALIGNMENT. (64字节的数)