WDM内核驱动程序模型分析
WDM驱动程序是Windows 2000操作系统重要的组成部分,它的正常工作需要有Windows 2000其它内核组件的支持,同时大部分的内核组件也必须同WDM驱动程序交互来完成它们的功能,为能够完整而清晰的说明WDM驱动程序的工作情况,本文首先说明Windows 2000操作系统的内核工作机理,同时将给出WDM驱动程序和内核其它组件的协同工作机制,最后分析WDM驱动程序的结构和工作流程。
1 Windows 2000内核组件工作模式
1.1 Windows 2000的设计思想
在现代操作系统中,应用程序和操作系统本身是分开的――操作系统代码运行在核心态并有权访问系统数据和硬件;应用程序运行在用户态,能够使用的接口和访问系统数据的权限都受到限制。当用户程序调用系统服务时,处理器捕获该调用,然后把调用的线程切换到核心态。当系统服务完成后,操作系统将线程描述表切换回用户态,允许调用者继续运行。
系统核心态部分的设计是丰富多样的。较典型的有以下几种:
A) 传统的单片式操作系统。系统被设计为一个单一的、庞大的软件系统,在内部组件之间有许多关联。这种软件系统依赖于许多系统组件。这种相互关联意味着要扩展系统,就需要对全部代码库进行大量修改。同时在单片式操作系统中大量的操作系统代码运行于同一内存空间,这就意味着任何操作系统组件都可能损坏正在被其它组件使用数据。
B) 分片式的操作系统。将操作系统发分为不同层次中的模块。每个模块提供一组功能函数其它模块调用。在某一层次上的代码只能调用较低层次上的代码。在一些了系统中,DEC公司的OpenVMS包括老的Multics操作系统,硬件甚至强制分层(使用多重、分级处理模式)。分层操作系统结构的优点是,由于每层代码只能访问较低层的接口(和数据结构),因此限制了使用无限权利的代码数量,这种结构也允许在最底层开始调试操作系统,然后一层一层的往上调试,直到整个操作系统工作正常。层次化结构也使增强操作系统更容易,因为修改或替代单独的一层,不影响系统的其它部分。
C) 客户服务器微内核模型。其思想是把操作系统分成若干个服务器进程,每个服务器进程完成一种服务,如内存管理服务、进程创建服务和处理器调度服务。每个“服务器”运行在用户态,等待客户提出服务请求。“客户”,可以是另一个操作系统的组件,也可以是应用程序,它通过发送一个消息给服务器来请求服务。运行在核心态的操作系统的微内核把该消息传递给服务器,该服务器执行操作,内核用另一种消息把结果返回用户。
Windows2000融合了分层操作系统和客户服务器微内核操作系统的特点。对性能影响很大的操作系统组件在核心态下运行。在核心态下,组件可以和硬件交互,也可以在组件之间交互,并且不会引起描述表切换和模式转变,例如,内存管理器、高速缓存管理器、对象及安全性管理器网络协议、文件系统(包括网络服务器和重定向程序)、所有线程和进程管理都运行的核心态。
当然,所有这些组件都应该受到保护,以避免给其它的应用程序侵扰,应用程序不能直接访问操作系统特性部分的代码和数据(尽管他们可以快速调用其它的内核服务)。这种保护使得Windows2000成为既坚固又稳定的应用程序服务器的原因之一。而且从核心操作系统服务的角度,如虚拟内存管理、文件操作、复合文件及打印共享来看,Windows2000作为工作站平台仍是迅速和敏捷的。
由于WDM驱动程序的标准应用平台是Windows 2000,是内核的重要组件之一并和其它内核组件有相当多的关联,为本文讨论的完整性,首先讨论Windows 2000的系统结构。
1.2 Windows 2000的系统结构
图1 Windows 2000 系统结构图
图1显示了Windows 2000内核系统结构图。Windows 2000是由内核模式和用户模式两类代码构成的。
用户态代码模式主要包括各个环境子系统:POSIX,OS/2,Win32。在这三个子系统中Win32比较特殊,它是Windows 2000的必须组件。实际上,其它两个子系统只是在需要时才被配置启动,而Win32必须始终处于运行状态。
A) 硬件抽象层(HAL) HAL是一薄层软件,它是硬件与操作系统其它部分的接口,是物理硬件资源的一种抽象。我们可以称之为“抽象硬件模型”。HAL通过动态链接库(DLL,Dynamic Link Library)实现,管理的项目包括:
w 片外高速缓存
w 定时器
w I/O总线
w 设备寄存器
w 中断控制器
w DMA控制器
各种系统组件使用HAL函数与CPU外的硬件打交道,这样就把平台特定的细节对系统的其余部分隐藏起来,可移植性极佳。特别地,HAL例程的使用使得内核和设备驱动程序在有相同CPU体系结构的平台上二进制代码兼容。
B) 内核(Kernel) 内核是整个操作系统的神经中枢,同执行体的其它部分不同,内核的大小从不会溢出内存。尽管执行一个中断服务例程可以中断内核,但内核的执行操作永远不会被其它正在运行的线程抢先。内核始终运行于核心态,其代码短小精悍,可移植性优异。内核主要由C语言编写,对那些需要尽可能快速执行的代码或非常依赖于处理器性能的任务保留用汇编语言编写。它提供管理以下功能的机制:
w 中断和异常处理
w 线程调度和同步
w 多处理机同步
w 定时控制
w 内核对象
内核的目标之一是提供一个严格定义的,可预测的操作系统基本要素和机制的低级操作。内核通过执行操作系统机制和避免制定策略而使其自身和执行体的其它部分分开。其主要通过提供两类对象来为其它部分服务:
w 调度者对象 负责同步性能并改变或影响线程调度。
w 控制对象 这些对象以某种方式控制操作系统的行为。
例如:设备驱动程序将使用的系统线程属于调度者对象,推迟过程调用、中断对象属于控制对象。
C) 执行体(Executive) 图1中的暗纹部分都属于执行体,但它们是完全独立的,只通过很好定义的接口来通讯。
1)系统服务接口
提供用户模式到内核模式的受控路径。在Win2000中,系统服务调度者使用一种基于CPU硬件异常机制的技术给用户模式代码提供对Executive服务的访问权。
2)对象管理器
执行体通过一个基于对象的接口给用户模式进程提供服务。文件、进程、线程、内存段等事物都是对象。对象管理器执行管理执行体对象的所有工作,包括创建和删除对象、维护全局对象名字空间,以及记录对指定对象有多少等待的引用。
3)配置管理器
从驱动程序开发者的角度看,配置管理器的主要工作是,使用登记(Registry)数据库,维持安装在机器上的所有硬件和软件资源。
驱动程序使用Registry作以下工作:
w 把自己标识为可信任的系统组件
w 查找和分配外围硬件
w 建立错误记录日志文件
w 启用驱动程序性能测量
4)进程管理器
进程是Windows2000中资源记录和安全性检查的单位。每个进程都有自己的虚拟地址空间和安全性标识。一个进程中包含一个或多个线程(Thread)。进程管理器处理进程和线程的创建、管理和销毁,它还为同步线程的活动提供一组标准的服务。
5)安全引用监视器
该组件强制系统的安全性策略。安全性引用监视器提供一组原语,其它组件可以调用它们来验证对对象的访问,检查用户特权和生成审查消息。驱动程序一般不与它打交道,I/O管理器在调用驱动程序中的任何例程之前处理这类工作。
6)虚拟内存管理器
在Windows2000下,每个进程有一个4GB的虚拟地址空间。这个空间的0~2GB是进程的私有代码和数据、堆栈和堆空间。它还含有进程使用的任何文件映射(File Mapping)对象和DLL。2~4GB只包含内核模式代码。虚拟内存管理器的任务之一是,使用按需分页的虚拟内存管理技术来维护地址空间映像。从驱动程序开发者的观点来看,它负责维护系统的堆空间。在DMA操作中,它还为驱动程序构造和操作缓冲区。
7)局部过程调用(LPC)
局部过程调用是一个消息传递机制,用于在同一台机器上的进程之间通信。LPC主要由受保护的子系统及其客户程序使用,设备驱动程序对LPC机制没有访问权。
8)I/O管理器
执行器组件把I/O请求从用户模式和内核模式线程转换成对各种驱动程序例程的合适顺序的调用。通过使用很好定义的接口,I/O管理器能够以相同的方式与所有驱动程序通信。
9)PnP管理器
PnP管理器由两部分组成:核心态PnP管理器和用户态PnP管理器。核心态PnP管理器同操作系统组件和内核驱动程序进行交互来动态的维护、配置、管理设备。用户态PnP管理器和用户安装组件进行交互,来配置和安装设备。用户态PnP管理器同时也和应用程序交互,例如:为一个应用注册一个设备变化通知消息用来在设备状态发生变化时通知应用程序。
10)Power管理器
Power管理器负责管理系统的电源使用。它负责维护一个系统范围内的电源策略,并负责Power IRP在系统中传递路径。Power管理器通过以下几点来考虑系统应处于什么状态――浅度睡眠,深度睡眠,关闭,还是正常运作 :
w 系统当前活动状况
w 系统电池状态
w 应用程序的电源请求
w 用户动作,例如:按下电源启动按钮
w 控制面板的设置
1.3 Windows 2000和WDM 驱动程序的设计目标
WDM设备驱动程序和Windows2000在设计目标上有很多方面是一致,尤其是系统输入输出管理器更是如此。这些目标包括以下几方面:
A) 优秀的可移植性
Windows2000用以下几种方法实现在不同的硬件结构和平台上的可移植性:
w 大多数的Windows2000组件提供一组内核服务例程, 供驱动程序和其它内核组件调用。 这些服务例程提供统一的编程接口,尽管这些服务例程底层的实现因为Windows版本不同而不同,而他们的编程接口保持稳定不变,这就使得基于其上的组件和驱动程序保持平台无关性和向后兼容性。
w Windows2000采用了分层设计,依赖于处理机体系结构和平台的系统底层部分被隔离的单独模块之中,所以系统的高层可以被屏蔽在千差万别硬件平台之外。提供操作系统可移植性的两个关键组件是HAL和内核。依赖于体系结构的功能(如线程描述表的切换)在内核中实现。在相同体系结构中,与计算机硬件交互的功能在HAL中实现。
w Windows 2000主要是用可移植C语言编写――操作系统执行体、实用程序、设备驱动程序使用C语言编写,图形子系统部份和用户界面是用C++语言编写。只有那些必须和系统硬件通信的操作系统部分(如中断处理程序),和性能极度敏感(如描述表切换)的部分使用汇编语言编写。汇编语言代码不仅存在于内核及HAL之中,而且存在于执行体中的少量区域(例如实现执行体里本地过程调用机构中的一个模块),甚至存在在一些用户态代码库中。多数Windows2000组件是用C语言编写,所以这个操作系统就容易在复杂指令CPU平台和精简指令计算机CPU的平台上移植。内核设备驱动程序也应当用C语言编写,这样它也可以在系统兼容的编译器上重新编译链接直接生成不同平台的设备驱动程序。WDM驱动程序同时可以在Windows98以后的系列操作系统中保持兼容,而不用重新编写代码。
w 为保证系统的可移植性,驱动程序应避免使用平台相关的C编译器进行编译。大体上讲,驱动程序的代码尽可能用标准C编写的.驱动程序应注意以下几点:
1) 驱动程序代码应避免使用平台相关的数据结构
2) 避免调用任何保持状态的C运行时库函数
3) 应尽量使用操作系统提供的服务例程同时尽量少的使用C运行时库函数
4) 不能在驱动程序中使用浮点运算
B) 软硬件可配置性
w 避免使用机器相关的硬编码,使得同一硬件可以在其它不同配置的机器上运行,这叫做硬件可配置性。
w 底层的驱动程序要为上层的各种驱动程序服务,它不能武断地猜测上层驱动的性质和特定实现,这叫做软件可配置性。
w Windows 2000的配置管理器提供了注册登记(Registry)数据库,在其中存储了系统的硬件、各种外设及其驱动程序的信息。驱动程序可以从中得到机器的硬件配置和系统中的其它驱动信息。即插即用管理器和内核驱动程序利用注册数据库来取得本机上硬件以及其它驱动程序的配置。Win2000驱动程序也把自己的设备信息以及机器相关的配置信息储存于此。
C) 总是可以被剥夺执行权,总是可以被中断
在Windows2000中内核组件代码按照以下优先级原则进行运行:
w 内核定义的线程优先级策略
系统中各线程都有其自身的优先级属性。大体上讲儿,大多数系统线程都有可变的优先级属性,它们总是可以被剥夺的而且它们和其它优先级相同线程一起被调度。一些系统中的线程具有实时优先级,它们可以抢先其它低优先级线程运行,同时它们也被优先级更高的线程抢先。
w 内核定义的中断请求级策略
内核同时把不同的软件中断和硬件中断赋予不同的优先级,这样某些高优先级的内核代码就能够运行在较高的中断请求级,某些特定的实时代码就能拥有很高的调度优先权。通过内核代码中运行的中断请求级可以确定相应的硬件优先级。内核模式代码应该始终可以被中断,较高优先级的中断随时都有可能发生,它可以中断当前执行的代码,立即投入运行。换句话说,在一定中断请求级上运行的代码在当前处理器上可以屏蔽相同或较低优先级的中断。内核定义了一种可移植的IRQL,如果处理器具有特殊的中断相关的特性(例如,第二时钟),则可以增加IRQL。按优先级排队中断,较高优先级的中断可以抢先较低优先级的中断服务,每个处理器IRQL设置决定了处理机可以接受哪些中断,IRQL也被用于同步访问核心态数据结构,当核心态线程运行时,它可以降低或提高处理器的IRQL。如果中断源的IRQL高于当前中断设置,则它的中断可以中断处理器;如果中断源的IRQL等于或低于当前中断级,则它的中断将被封锁或屏蔽,直到一个正在执行的线程降低了IRQL。
操作系统的可以被剥夺和可以被中断的设计目标带来的好处是最大限度提高平均性能,任何中断服务例程都可以被更高中断请求级的中断服务例程中断,任何线程都可以被更高优先级的线程抢先。
D) 多处理器安全
在Windows2000多CPU操作系统平台,以下几种情况有可能发生:
在对称多处理多平台上,每个处理机都可以访问内存,发生中断,访问输入输出控制寄存器。(比较而言,在非对称多处理平台上,只有主CPU可以处理中断。)
Windows2000被设计为可以不加修改地运行在单处理器和多处理器平台上,所有驱动程序也必须安全的实现多处理器安全,避免被一个处理机访问和修改的存储器同时被另一台处理器访问和改变,比如说,一个设备的中断同时在两个处理器上发生,那么在一个处理器上特定优先级上的中断服务例程必须提供某种机制排他的访问临界区。在多处理器情况下,驱动程序的输入和输出请求可能是重叠的,这就是说,一台处理机正在处理输入请求,而另一个处理器在和设备通信,在这种情况下,所有的驱动程序必须同步对共享的资源进行访问(如果有必要的话)。内核组件提供一种叫自旋锁的机制,用来在多处理机平台上保护共享数据,使用自旋锁有以下两条原则:
w 在一个时刻只有一个例程能够获得此自旋锁,只有持有它的例程可以访问被自旋锁保护数据,其它例程必须等待这个例程是释放自旋锁,然后重新获得后,才能去接触共享的数据。
w 每一个系统中的自旋锁都有相关的中断请求级,只有运行在这个中断请求级上的例程才有可能获得自旋锁。
E) 基于面向对象的设计的思想
w Windows2000是基于面向对象的思想设计,执行体中的不同组件定义了许多对象类型,每一个组件同时提供了操纵这些对象方法,组件不允许直接操作其它组件的对象,为使用其它组件的对象,必须使用其提供的接口。严格遵循这些原则有利于增强Windows2000的可移植性和灵活性,例如,未来版本的Windows操作系统有可能部分或者全部的重新定义这些对象结构,这些被重写的对象版本将不会对现有的执行体组件产生影响,换句话说,操作系统中组件的通信服务方式也是基于接口,驱动程序同样应该避免使用引用或者指针操作对象本身。
w 和操作系统一样,驱动程序和他们的设备也是基于面向对象对象设计的,对系统的其它组件而言(包括用户代码),同设备联系被定义为一种打开文件操作,在输入输出子系统中,每一个驱动程序(逻辑,虚拟,和物理设备)都由“设备对象”来表示,每一个驱动程序加载映像都用“驱动对象”表示。输入输出管理器定义这些对象。
F) 依可重用的包(输入输出请求包,IRP,I/O Request acket)驱动的I/O
输入输出管理器的主要任务是接收输出请求(通常来自于用户应用程序),创建IRP来代表它,将这些IRP引导到正确的服务例程来处理;并且跟踪他们直到他们被完成,最后向发起者返回状态参数。输入输出管理器,插即用管理器,电源管理器使用IRP来同核心态驱动程序进行通信,并且允许驱动程序之间进行通信。因此每一个IRP都由两大部成:
w 固定部分:输出输入管理器用来保持原始输入请求信息,例如调用者线程ID和调用参数,打开文件指定的设备对象,等等。固定部分同时包含一个输入输出状态,驱动程序用于返回输入输出操作的结果。
w 可变部分:数量不同的输入输出堆栈,用于支持分层的驱动程序结构。
G) 支持异步I/O
w I/O管理器提供异步I/O支持以便I/O传输的发起者可以不必等待I/O操作的完成而继续它的工作。异步I/O提高了用户应用的性能,也提高了驱动程序的性能。异步I/O使得驱动程序不必按照I/O请求到来的顺序进行操作。这样,驱动程序内部必须维护I/O请求当前处理的阶段状态。
w 内核驱动程序处理I/O请求的次序不一定和这些I/O请求到达I/O管理器的次序相同。I/O管理器或高层驱动也可以将大的数据请求块划分为多个小块来进行处理。而且,内核驱动并不一定串行的处理这些I/O请求。这就是说,驱动程序不一定等到前一个IRP完成才去处理下一个IRP。
2 WDM驱动程序和I/O子系统的协同工作机制分析
在Windows驱动程序模型(WDM)出现之前Microsoft为它的两个系列操作系统提供了不同的驱动程序模型,他们分别是:Windows3.1和Windows95的Vxd以及NT式的驱动程序。两种驱动程序的架构是如此的不同,以至于程序员必须为每个设备在两种系统上分别编写、编译、调试驱动代码,这对于程序员来说是一个很大的负担。
为统一驱动程序架构,减轻程序员的负担,微软推出了新型的WDM驱动程序模型,现在的Windows 98(第二版)以及Windows 2000均采用了这个模型。而且微软已经宣布未来的Windows系列操作系统的驱动模型将是基于WDM构架的。
Windows驱动程序模型(WDM)的设计思想是非常先进的,它迎合了当前的高级操作系统的设计的先进之处:
w 它是支持多处理器架构的,完全支持对称多处理
w 它是处理器无关的,支持多种处理器架构。
w 它完全支持即插即用和电源管理
尽管对于用户来说Windows 98 and Windows 2000下的模型是相同的,但是他们的工作机制是完全不同的,以下将分别讨论。
2.1 Windows2000下的WDM驱动程序和I/O子系统的协同工作机制分析
图2 Windows 2000 I/O请求简化模型
如图2所示,Windows 2000是分态的操作系统,因此在系统中代码运行在两种模式下:用户态和核心态。用户应用程序运行在用户态,操作系统代码(如系统服务和设备驱动程序)在核心态下运行。在核心态处理器模式中,代码可以访问所有系统内存和所有的CPU指令。操作系统软件的特权级别高于应用程序软件,通过这种机制,使得应用程序的不当行为在总体上不会破坏系统的稳定性。Windows 2000对核心态运行的组件不提供任何保护。换句话说,一旦处于核心态,系统代码就可以完全访问系统内存空间,并在访问时不受安全性的约束。
Win32 应用程序运行在用户态,操作系统严格限制应用程序的行为,防止其对其它应用和系统代码的破坏。因此用户态程序只能调用Win32子系统提供的API来同设备交互,Win32子系统模块中的服务代码然后调用平台无关的系统服务例程来切入核心态并调用核心态组件(I/O管理器)提供的服务例程。当请求传递到I/O管理器时,它创建一个IRP(I/O Request acket)并将其传递到适当的驱动程序去,并给应用程序一个消息,通知这次I/O操作还没完成。应用程序收到通知后或者继续执行,或者挂起等待。在任何一种情况下驱动程序独立的执行来服务应用程序。
驱动程序最终需要访问硬件来完成I/O请求,在程序控制的I/O方式下的读请求,驱动程序的动作为读I/O端口或寄存器。尽管驱动程序运行在核心态下,可以直接和硬件交互,但在大多数情况下,为保持其可移植性,驱动程序一般调用硬件抽象层提供的例程来和硬件交互。硬件抽象层提供一种与处理器平台无关的方法来执行实际的I/O 操作。例如,在Intel x86处理器平台上,硬件抽象层使用IN指令,在Alpha平台上,它将使用MEMORY读取操作。
驱动程序完成I/O操作后,它将调用一个特殊的内核服务例程来完成IRP。这时,因这个I/O请求挂起的任何Win32应用将继续执行。
2.2 Windows 98下的WDM驱动程序和I/O子系统的协同工作机制分析
在Windows98中,操作系统内核被称为虚拟机管理器(VMM),因为它的主要任务是在单一的物理硬件上创建一个或多个“虚拟”的机器来共享这些硬件。而虚拟设备驱动程序(VxD)的设计初衷也是虚拟一个特定的硬件来辅助虚拟机管理器产生一种假象,那就是:每一个虚拟机都拥有一套完整的硬件。这种设计源于Windows 3.0,并在Windows 95和Windows 98中沿用。
Windows98并不象Windows 2000那样统一有序的处理I/O请求。Windows98在处理磁盘、串口、键盘等等这些不同的设备时采用的机制有很大不同。并且如图3所示,Windows98在处理32位和16位应用的I/O请求时,采用的方法也是根本不同的。图3中的左列展示了系统是如何处理32位应用的I/O请求的。在Windows98中,对于不同的设备,应用程序采用不同的机制来和驱动程序进行交互。应用程序可能调用一个Win32 API例如ReadFile来读设备,但这些设备仅包括磁盘文件,串口,和一些由WDM驱动程序驱动的设备。但对于其它设备,应用程序只能通过基于DeviceIoControl的一些特别的机制来和设备通讯。即使是同样调用ReadFile,应用程序使用一种机制来和磁盘驱动交互,采用另一种机制和串口驱动通讯。而由WDM驱动的设备的I/O请求方法和前两种又有本质的不同。
图3的中列和右列展示了系统是如何处理16位(Windows 3.1)和DOS应用程序的I/O请求的。在两种情况下,应用程序都直接或间接的和用户态驱动程序进行交互,用户态驱动程序将请求传递到核心态虚拟设备驱动,再由核心态虚拟设备驱动代理应用程序和硬件直接交互。
虽然在Windows 2000中以一种统一的方式(IRP)在内核中传递I/O操作,但在Windows 98中,即使在核心态也没有统一方法的来代表I/O请求。
但对于WDM驱动程序来说,Windows 98在处理方式上通过一个系统模块(NTKERN.VXD)来模仿Windwos 2000内核的处理方法。这个模块提供了Windows 2000内核的大部分服务例程的仿真,并模拟I/O管理器来产生和发送IRP。WDM驱动程序几乎无法区分两种平台的差异。
图3 Windows98 I/O请求简化模型
3 分层的设备驱动程序和即插即用设备栈
3.1 分层的设备驱动程序
图4说明用户的动作最后如何由设备驱动程序处理。应用程序对设备I/O进行Win32调用,这个调用由I/O系统接收。I/O 管理器发送IRP来请求驱动程序的处理。
在最简单的情况下,I/O管理器只是把IRP传递给一个设备驱动程序,这个设备驱动程序和硬件交互,并完成IRP的处理。I/O管理器把数据和结果返回给Win32和用户应用程序。
通常IRP由分层的驱动程序栈来处理。高层的驱动程序把请求划分成更简单的请求并传递给下层驱动程序。例如,在文件系统驱动程序中,最高层的驱动程序知道文件如何在磁盘上表示,但不知道如何得到数据的细节。中间层次的驱动程序进一步处理请求,将一个IRP中的请求划分为若干个小的请求并传给下层驱动程序。最后,最低层的驱动程序与硬件打交道。
在任何地的地方,驱动程序被设计为尽可能的通用。例如,SCSI端口知道如何把磁盘数据请求转换成SCSI请求,但是,它再向SCSI小端口驱动程序发出SCSI请求,这些小端口驱动程序知道如何访问各种SCSI硬件。
过滤驱动程序是一种中间驱动程序,它位于其它的驱动程序层次之间,提供一些附加的功能,而不影响其它驱动程序。例如,过滤驱动程序可以用以提供容错的磁盘访问,在这个驱动程序中,数据被写到两个不同的物理磁盘,保证数据的冗余保存。
图4 驱动程序分层简化模型
3.2 即插即用设备栈
Windows驱动程序模型重新定义驱动程序分层,以适用于即插即用系统。一个设备栈代表处理请求的驱动程序层次,如图5所示。总线驱动程序控制对总线上的所有设备的访问。例如,如果访问USB设备,必须使用USB总线驱动程序。
总线驱动程序负责枚举它的总线,这意味着发现总线上的全部设备和检测设备何时被添加或删除。总线驱动程序创建一个物理设备对象来代表它发现的设备。一些总线驱动程序简单的控制对总线的访问,一旦有了控制权,就可以对总线做任何想要的工作。在其它情况下,总线驱动程序为我们处理总线上的所有事务。
功能驱动程序知道如何控制设备的主要功能,它分层在总线驱动程序的上面。功能驱动程序创建一个功能设备对象,放在设备栈中,在USB的情况下,功能驱动程序必须使用USB类驱动程序访问它的设备。但是,在其余情况下,一旦总线驱动程序接管,功能驱动程序可以直接访问硬件。有多个功能驱动程序分层在第一个功能驱动程序之上是非常可能的。
各种类型的过滤驱动程序可以插在设备栈中。对总线上的所有设备,总线过滤驱动程序被加在总线驱动程序之上;而对于特定类的所有功能驱动程序,添加类过滤驱动程序。设备过滤驱动程序仅对特定的设备添加。上层的过滤驱动程序在功能驱动程序之上,而底层过滤驱动程序在功能驱动程序之下。
用户的请求总是在设备栈的顶部进入。假设用户程序标志了一个它想访问的功能设备,I/O管理器保证它的全部请求都发送到设备栈的顶部,这样任何高层的过滤驱动程序或功能驱动程序首先得到处理这些请求的机会。
图5 典型的即插即用设备栈
3.3 标准总线驱动程序和类驱动程序
图6给出了Windows提供的主要类驱动程序和总线驱动程序,它们是作为总线驱动程序和功能驱动程序执行的通用驱动程序。在大多数情况下,主系统驱动程序和与硬件接口的另一个驱动程序或另一类驱动程序一起使用。这些辅助驱动程序通常成为小驱动程序。
图6 总线驱动程序和类驱动程序分类
高级配置和电源接口(ACPI)总线驱动程序与PC ACPI BIOS打交道,枚举系统中的设备并控制它们的功率使用。PCI总线驱动程序枚举和配置PCI总线上的设备。PnPISA总线驱动程序对可以使用即插即用配置的ISA设备作类似的工组。
流类驱动程序提供访问高带宽,时间关键和视频,音频数据的基础。
IEEE 1394枚举和控制IEEE 1394高速总线,这个总线驱动程序使用端口驱动程序访问IEEE 1394控制电路。IEEE 1394客户驱动程序发出IEEE 1394请求块来控制它们的设备。
USB枚举和控制低速的USB总线。主机控制器驱动程序作为访问主要的两类USB主机控制器的标准。
SCSI和CDROM驱动程序用于访问硬盘,软盘,光盘和DVD。
人工输入设备类设备驱动程序提供输入设备的一个抽象视图。
4 WDM驱动程序的结构及主要例程
WDM驱动程序的主要工作如下:
w 初始化自己。
w 创建和删除设备。
w 处理Win32打开和关闭文件句柄的请求。
w 串行化对设备的访问。
w 访问硬件。
w 调用其它驱动程序。
w 取消I/O请求。
w 超时I/O请求。
w 处理一个可热插拔的设备被加入或删除的情况。
w 处理电源管理请求。
以下分别分析了完成以上功能的WDM驱动程序模块和工作原理:
A) WDM驱动程序入口点和回调例程
WDM驱动程序有两个主要的初始化入口点---- DrvierEntry和AddDevice例程。DrvierEntry注册其它回调例程的。在运行过程中,内核会调用不同的回调例程来完成不同的任务。
下表列出了主要的回调例程:
DriverEntry 初始驱动程序入口点,设置主要的回调例程。
AddDevice 一个新的即插即用设备(PnP)被添加。
I/O请求包处理例程 被调用用来处理希望处理的IRP
Unload 卸载驱动程序
StartIo 串行处理IRP的回调例程
ISR 中断服务例程
DpcForIsr 延时过程调用例程,完成一个I/O请求和开始另一个中断驱动的传输
临界断例程 同步一个处理器上的中断执行。由低级IRQL的任务调用来与硬件交互
Cancel 取消一个IRP
Completion 当一个低层驱动程序完成一个IRP的处理时被调用,这允许当前的驱动程序作更多的工作
AdapterControl 当一个DMA通道适配器可用时调用
ControllerControl 当一个控制器空闲时调用
Timer 定时器回调例程
CustomerTimerDpc TimerDpc的回调例程
CustomerDpc 通常用于处理工作队列
即插即用通知 当设备PnP状态发生变化时被调用
电源通知 当设备Power状态发生变化时被调用
ConfigCallBack 查询设备硬件描述回调
B) I/O系统服务派发(Dispatch)例程
驱动程序的DriverEntry例程必须设置一系列的回调例程来处理IRP。下表列出了常见的Win32设备I/O函数和它们对应的IRP:
CreateFile “Create” IRP
CloseHandle “Close” IRP
ReadFile “Read” IRP
WriteFile “Write” IRP
DeviceIoControl “IOCTL” IRP
内部的IOCTL IRP
“Create”, “Close”, “Read”, “Write”, “IOCTL”和内部的IOCTL的处理程序通常称为分发例程,因为它们通常仅执行IRP的一些初始化处理,如检查所有的参数是否合法,然后把IRP分发到驱动程序的其它地方去处理。IRP通常需要串行处理,使得驱动程序以一种安全的方式与硬件打交道。
5 WDM驱动程序的工作流程分析
A) 创建设备
设备的创建一般在即插即用管理器发现设备时进行。类似的,在执行过程中,当驱动程序卸载或者PnP管理器告诉设备正在被删除时,我们要删除这些设备对象。
大多数的WDM设备对象都是在PnP管理器调用AddDevice入口点时创建的。这个例程在插入新设备和安装INF文件指示这个驱动程序是要运行的驱动程序时被调用。在此之后,一系列的PnP IRP被发送到驱动程序,指示设备应当何时启动和查询它的功能。最后,一个删除设备PnP IRP指示设备已经被删除,所以驱动程序删除设备对象。
NT式驱动程序在它们想创建设备的时候创建设备。它们的DriverEntry例程通常寻找自己的硬件,找到后创建设备对象。
在创建设备后,为了使Win32可见,我们必须为每个设备创建符号链接。可以采用两种方法创建符号链接:第一种方法是采用显示的“硬编码”符号链接名,
用户态程序必须类似的把设备名硬编码到源代码中。另外一种方法是使用设备接口,每个设备接口由一个全局唯一标志符标志。把设备注册为一个特定的设备接口就创建了一个符号链接。用户态设备可以取得拥有此GUID的设备。
B) 硬件资源分配
低层的驱动程序需要知道为他们分配了哪些硬件资源。最常见的硬件资源是I/O端口,存储器地址,中断和DMA线。
处理PnP IRP的WDM驱动程序在收到“启动设备”的PnP IRP时被告知设备的资源。NT式的驱动程序必须自己发现资源并请求使用这些资源。
C) 驱动程序的分层调用
WDM驱动程序花大量的时间访问其它驱动程序。一个即插即用设备是在一个设备对象栈中,把IRP沿设备栈传递到下一个驱动程序是很常见的。
一些类型的IRP(如即插即用,电源管理等)常常立即传递到设备栈中的下一个设备。
在另一些情况下,驱动程序的工作是通过调用设备栈中的下一个设备实现的。例如,USB客户驱动程序通常通过沿设备栈向下传递IRP来调用USB总线驱动程序。
D) 串行化处理
访问硬件的任何设备必须使用某种机制保证驱动程序的不同部分不同时访问相同的硬件。在一个多处理器系统中,“Write”IRP处理程序可以同时在两个不同的处理器上运行。如果它们两个都试图访问相同的硬件,则会出现不可预料的结果。同样,如果一个“Write”IRP正在试图访问硬件的同时发生了中断,那么,两个动作可能会相互影响。
内核采用两种机制来同步这些冲突操作:
w 第一种是采用临界段例程,使用这些临界段例程保证代码不会被中断处理程序中断。这些临阶段例程在内部使用了中断自旋锁,所以可以保证多处理器同步。
w 第二种是使用StartIo例程串行处理IRP,每个设备对象有一内部的IRP队列,驱动程序的派发例程将IRP插入这个队列中。内核I/O管理器从这个队列一个个的取出IRP,并把它们传递到驱动程序的StartIo例程。所以StartIo例程串行的处理IRP,保证不与其它的IRP处理例程冲突。
如果一个IRP已经在一个队列中,此时用户线程突然中止或其调用Win32函数取消了这次I/O,驱动程序必须取消这个IRP。这可以通过给每一个排队的IRP挂接一个取消回调例程来实现。
如果用户态程序关闭了设备的文件句柄,而这个设备有重叠请求在等待,则必须要有“清理”例程。清理例程负责取消与一个文件句柄关联的所有IRP。
E) 访问硬件
在取得了I/O端口和内存地址后,访问硬件就显得比较直接,但是由于Windows 是多任务系统,在一般情况下占用处理器的时间应小于50微秒,需要长时间的硬件访问应采用系统线程的方法。
因为中断服务例程的IRQL(中断请求级)很高,故在中断服务例程中应尽量减少访问硬件时间,而且很多内核例程在这个中断请求级上被禁止调用,故一些中断善后工作在延迟过程调用(DPC)中实现。
F) 即插即用支持技术
即插即用(PnP)是计算机系统自动识别和适应硬件配置的改变的技术,完全的即插即用不仅需要硬件的支持,同时也需要软件的支持。对用户来说,他可以自由的向计算机中添加和删除设备,而无需使用笨拙的手工配置同时不必深入了解复杂的计算机软硬件知识。
即插即用需要设备硬件,系统软件,和驱动程序的支持。PnP的发起者定义了关于易识别设备和基本系统组件的PnP工业标准。这些标准主要阐述了系统软件和驱动程序如何支持即插即用。
系统软件和驱动程序对即插即用的支持主要提供以下功能:
w 自动识别已安装的设备
w 硬件资源的动态重分配
w 自动加载正确的驱动程序
w 提供使得在硬件环境发生变化时,驱动程序和用户模式代码得到通知的机制
PnP驱动程序是对PnP支持很重要的部分。PnP驱动程序必须提供PnP调用入口,处理繁多的PnP IRPs,并且遵循PnP的设计规范。
图7说明了操作系统的组件如何同WDM驱动程序一起提供即插即用的支持。从图7可以看出PnP管理器由两部分组成:核心态PnP管理器和用户态PnP管理器。核心态PnP管理器负责同操作系统组件和WDM驱动程序交互来配置、管理和维护设备。用户态PnP管理器主要和用户态安装程序一起来简化设备的安装工作。同时,PnP管理器只有和WDM驱动程序紧密合作,才能使得系统对即插即用有完整的支持。WDM驱动程序遵循Windows 驱动模型规范并且可以不加修改的同时运行在Windows2000 和Windows98中。为支持PnP,WDM驱动程序的设计必须遵守以下原则:
w WDM驱动程序必须遵循以设备为中心的即插即用驱动程序模型。PnP管理器在全局的基础上管理系统的设备,并调用驱动程序来服务它们的设备。当PnP管理器发现设备时,驱动程序的AddDevice例程将被PnP管理器调用来服务这个设备。
w WDM驱动程序必须遵守PnP软件设计原则例如:驱动程序必须拥有专门的例程来处理繁多PnP IRP并正确传递这些PnP IRP,这是一项非常繁琐复杂的工作。
w WDM驱动程序不应自已声明硬件资源,此项工作应由PnP管理器在系统范围公断并调整资源后完成。
w WDM驱动程序应该是模块化的,驱动程序的各个例程都要求有独立的入口和模块以方便PnP管理器在需要时调用。
为配合PnP管理器的工作,WDM驱动程序必须自己维护多个PnP状态。在PnP系统中,当设备被配置、启动、停止时设备会在不同的PnP状态之间转换,如图8所示。
正确的维护本设备的PnP状态和响应相应的PnP IRP的状态迁移指示是WDM驱动程序对PnP支持的关键。
图7 WDM驱动程序中的PnP组件
G) 电源管理支持技术
电源管理是在系统范围内的有效使用电源的综合方法。拥有硬软件电源管理支持的计算机系统能够拥有以下功能:
w 最短的启动和关机延迟。
w 极大提高的整体电源使用效率和更长的电池寿命。
w 更安静的操作。
在Windows2000和Windows98以及其它支持电源管理的操作系统中,计算机和它的外设被维持在可能的最低电源使用水平上来完成它们当前的工作。WDM驱动程序和操作系统合作来管理它们的设备电源。如果所有的驱动程序都支持电源管理,那么操作系统就可以在全局的基础上维持系统的电源消耗,来节约电量的使用,更快的启动和关机,而且可以随时被从睡眠中唤醒。
支持PnP的驱动程序必须支持电源管理,反之亦然。电源管理和即插即用是综合在一起的且是相互依赖的。
电源管理在两个层次上工作,第一层在独立的WDM驱动程序中,第二层在以电源管理器为核心的系统整体范围内。
图8 设备的PnP状态迁移图
电源管理器作为操作系统内核的一部分在系统范围内管理电源水平。所有的驱动程序都支持电源管理,电源管理器就可以在系统范围内统一管理电源的消耗,不仅使系统在适当的时候进入完全开启和完全关闭状态,而且可以在几个不同的中间状态中进行睡眠。
设备电源管理应用于单独的设备。支持电源管理的驱动程序可以将它的设备在使用时加电,并且在不用时掉电。如果硬件支持,则设备可以在驱动程序的控制下进入中间的设备电源状态。
电源管理和即插即用一样同样需要硬件,系统软件和驱动程序的共同支持。硬件的支持在“高级配置与电源管理接口规范”(ACPI 参考http://www.teleport.com/~acpi2))中有详细说明。WDM驱动程序和电源管理器从软件上维护设备及系统的电源状态。图9说明了WDM驱动程序如何和电源管理器合作进行电源管理的。
用户可以通过控制面板和系统API来向电源管理器输入他们的电源命令。电源管理器管理系统范围内的电源策略----支配系统电源使用的规则。通过从控制面板和系统API得到信息,电源管理器可以决定应用程序正在或者可能需要使用不同的设备,从而适当的调整系统的电源策略。
当电源管理器请求改变系统电源状态时,驱动程序应当响应请求并把它们的设备投入到对应的设备电源状态。
电源状态指明了电源消耗的水平。电源管理器设置系统电源状态,驱动程序设置设备电源状态。WDM驱动程序定义了同ACPI说明相同的五种系统的电源状态(从S0到S5)和四种设备电源状态(从D0到D5)。同即插即用的支持相同,正确的维护本设备的电源状态和响应相应的Power IRP的电源状态迁移指示是WDM驱动程序对电源管理支持的关键。
图9 WDM电源管理组件
6 小结
WDM设备驱动程序工作在Windows2000或Windows98的内核状态,和系统的其它核心部件打交道。理解Windows2000的设计目标、实现及其结构对编制合格的驱动程序至关重要。
本文首先介绍了Windows2000操作系统的设计目标、特点,同时着重介绍了内核模式的组成和各构件的功能。作为系统重要组成部分的WDM驱动程序在很多方面和操作系统的设计目标是一致的。本文第二节简要地介绍了WDM驱动程序的分类以及驱动程序的特点、设计目标。由于WDM驱动程序工作在Windows2000和Windows98下时和I/O子系统的交互的差异(由于操作系统设计的差别),本文分别讨论了WDM驱动程序在不同操作系统下的工作模式。指出了它们的异同点。WDM驱动程序的即插即用设备栈和标准总线驱动程序也是本文讨论的内容之一。在本文的最后,本文给出了WDM驱动程序的结构和主要例程,并分析了WDM驱动程序的工作原理。
从本文中可以看出,要编制WDM设备驱动程序必须掌握大量的预备知识,理解和领会各种复杂的内核例程和许多繁杂的驱动程序开发原则,这就带来了本文开始时讨论过的弊病:需要专门的人力投入,开发周期长,正确性、可靠性难以保证,升级困难,可读性差等。
如何更高层次上利用DDK,实现通用部分的封装(如资源查找和分配,设备物理内存及I/O空间到NT虚拟空间的映射),则可以减少重复开发的工作量,正确性和可靠性更容易得到保障。在此基础上,利用设备的各种自有特征,自动生成驱动程序成为可能,使驱动程序的开发更为简化,开发周期更短。集成化的开发环境是高效编程的保证之一,恰恰是DDK所欠缺的,在研究中将力图解决这个问题。
WDM驱动程序是Windows 2000操作系统重要的组成部分,它的正常工作需要有Windows 2000其它内核组件的支持,同时大部分的内核组件也必须同WDM驱动程序交互来完成它们的功能,为能够完整而清晰的说明WDM驱动程序的工作情况,本文首先说明Windows 2000操作系统的内核工作机理,同时将给出WDM驱动程序和内核其它组件的协同工作机制,最后分析WDM驱动程序的结构和工作流程。
1 Windows 2000内核组件工作模式
1.1 Windows 2000的设计思想
在现代操作系统中,应用程序和操作系统本身是分开的――操作系统代码运行在核心态并有权访问系统数据和硬件;应用程序运行在用户态,能够使用的接口和访问系统数据的权限都受到限制。当用户程序调用系统服务时,处理器捕获该调用,然后把调用的线程切换到核心态。当系统服务完成后,操作系统将线程描述表切换回用户态,允许调用者继续运行。
系统核心态部分的设计是丰富多样的。较典型的有以下几种:
A) 传统的单片式操作系统。系统被设计为一个单一的、庞大的软件系统,在内部组件之间有许多关联。这种软件系统依赖于许多系统组件。这种相互关联意味着要扩展系统,就需要对全部代码库进行大量修改。同时在单片式操作系统中大量的操作系统代码运行于同一内存空间,这就意味着任何操作系统组件都可能损坏正在被其它组件使用数据。
B) 分片式的操作系统。将操作系统发分为不同层次中的模块。每个模块提供一组功能函数其它模块调用。在某一层次上的代码只能调用较低层次上的代码。在一些了系统中,DEC公司的OpenVMS包括老的Multics操作系统,硬件甚至强制分层(使用多重、分级处理模式)。分层操作系统结构的优点是,由于每层代码只能访问较低层的接口(和数据结构),因此限制了使用无限权利的代码数量,这种结构也允许在最底层开始调试操作系统,然后一层一层的往上调试,直到整个操作系统工作正常。层次化结构也使增强操作系统更容易,因为修改或替代单独的一层,不影响系统的其它部分。
C) 客户服务器微内核模型。其思想是把操作系统分成若干个服务器进程,每个服务器进程完成一种服务,如内存管理服务、进程创建服务和处理器调度服务。每个“服务器”运行在用户态,等待客户提出服务请求。“客户”,可以是另一个操作系统的组件,也可以是应用程序,它通过发送一个消息给服务器来请求服务。运行在核心态的操作系统的微内核把该消息传递给服务器,该服务器执行操作,内核用另一种消息把结果返回用户。
Windows2000融合了分层操作系统和客户服务器微内核操作系统的特点。对性能影响很大的操作系统组件在核心态下运行。在核心态下,组件可以和硬件交互,也可以在组件之间交互,并且不会引起描述表切换和模式转变,例如,内存管理器、高速缓存管理器、对象及安全性管理器网络协议、文件系统(包括网络服务器和重定向程序)、所有线程和进程管理都运行的核心态。
当然,所有这些组件都应该受到保护,以避免给其它的应用程序侵扰,应用程序不能直接访问操作系统特性部分的代码和数据(尽管他们可以快速调用其它的内核服务)。这种保护使得Windows2000成为既坚固又稳定的应用程序服务器的原因之一。而且从核心操作系统服务的角度,如虚拟内存管理、文件操作、复合文件及打印共享来看,Windows2000作为工作站平台仍是迅速和敏捷的。
由于WDM驱动程序的标准应用平台是Windows 2000,是内核的重要组件之一并和其它内核组件有相当多的关联,为本文讨论的完整性,首先讨论Windows 2000的系统结构。
1.2 Windows 2000的系统结构
图1 Windows 2000 系统结构图
图1显示了Windows 2000内核系统结构图。Windows 2000是由内核模式和用户模式两类代码构成的。
用户态代码模式主要包括各个环境子系统:POSIX,OS/2,Win32。在这三个子系统中Win32比较特殊,它是Windows 2000的必须组件。实际上,其它两个子系统只是在需要时才被配置启动,而Win32必须始终处于运行状态。
A) 硬件抽象层(HAL) HAL是一薄层软件,它是硬件与操作系统其它部分的接口,是物理硬件资源的一种抽象。我们可以称之为“抽象硬件模型”。HAL通过动态链接库(DLL,Dynamic Link Library)实现,管理的项目包括:
w 片外高速缓存
w 定时器
w I/O总线
w 设备寄存器
w 中断控制器
w DMA控制器
各种系统组件使用HAL函数与CPU外的硬件打交道,这样就把平台特定的细节对系统的其余部分隐藏起来,可移植性极佳。特别地,HAL例程的使用使得内核和设备驱动程序在有相同CPU体系结构的平台上二进制代码兼容。
B) 内核(Kernel) 内核是整个操作系统的神经中枢,同执行体的其它部分不同,内核的大小从不会溢出内存。尽管执行一个中断服务例程可以中断内核,但内核的执行操作永远不会被其它正在运行的线程抢先。内核始终运行于核心态,其代码短小精悍,可移植性优异。内核主要由C语言编写,对那些需要尽可能快速执行的代码或非常依赖于处理器性能的任务保留用汇编语言编写。它提供管理以下功能的机制:
w 中断和异常处理
w 线程调度和同步
w 多处理机同步
w 定时控制
w 内核对象
内核的目标之一是提供一个严格定义的,可预测的操作系统基本要素和机制的低级操作。内核通过执行操作系统机制和避免制定策略而使其自身和执行体的其它部分分开。其主要通过提供两类对象来为其它部分服务:
w 调度者对象 负责同步性能并改变或影响线程调度。
w 控制对象 这些对象以某种方式控制操作系统的行为。
例如:设备驱动程序将使用的系统线程属于调度者对象,推迟过程调用、中断对象属于控制对象。
C) 执行体(Executive) 图1中的暗纹部分都属于执行体,但它们是完全独立的,只通过很好定义的接口来通讯。
1)系统服务接口
提供用户模式到内核模式的受控路径。在Win2000中,系统服务调度者使用一种基于CPU硬件异常机制的技术给用户模式代码提供对Executive服务的访问权。
2)对象管理器
执行体通过一个基于对象的接口给用户模式进程提供服务。文件、进程、线程、内存段等事物都是对象。对象管理器执行管理执行体对象的所有工作,包括创建和删除对象、维护全局对象名字空间,以及记录对指定对象有多少等待的引用。
3)配置管理器
从驱动程序开发者的角度看,配置管理器的主要工作是,使用登记(Registry)数据库,维持安装在机器上的所有硬件和软件资源。
驱动程序使用Registry作以下工作:
w 把自己标识为可信任的系统组件
w 查找和分配外围硬件
w 建立错误记录日志文件
w 启用驱动程序性能测量
4)进程管理器
进程是Windows2000中资源记录和安全性检查的单位。每个进程都有自己的虚拟地址空间和安全性标识。一个进程中包含一个或多个线程(Thread)。进程管理器处理进程和线程的创建、管理和销毁,它还为同步线程的活动提供一组标准的服务。
5)安全引用监视器
该组件强制系统的安全性策略。安全性引用监视器提供一组原语,其它组件可以调用它们来验证对对象的访问,检查用户特权和生成审查消息。驱动程序一般不与它打交道,I/O管理器在调用驱动程序中的任何例程之前处理这类工作。
6)虚拟内存管理器
在Windows2000下,每个进程有一个4GB的虚拟地址空间。这个空间的0~2GB是进程的私有代码和数据、堆栈和堆空间。它还含有进程使用的任何文件映射(File Mapping)对象和DLL。2~4GB只包含内核模式代码。虚拟内存管理器的任务之一是,使用按需分页的虚拟内存管理技术来维护地址空间映像。从驱动程序开发者的观点来看,它负责维护系统的堆空间。在DMA操作中,它还为驱动程序构造和操作缓冲区。
7)局部过程调用(LPC)
局部过程调用是一个消息传递机制,用于在同一台机器上的进程之间通信。LPC主要由受保护的子系统及其客户程序使用,设备驱动程序对LPC机制没有访问权。
8)I/O管理器
执行器组件把I/O请求从用户模式和内核模式线程转换成对各种驱动程序例程的合适顺序的调用。通过使用很好定义的接口,I/O管理器能够以相同的方式与所有驱动程序通信。
9)PnP管理器
PnP管理器由两部分组成:核心态PnP管理器和用户态PnP管理器。核心态PnP管理器同操作系统组件和内核驱动程序进行交互来动态的维护、配置、管理设备。用户态PnP管理器和用户安装组件进行交互,来配置和安装设备。用户态PnP管理器同时也和应用程序交互,例如:为一个应用注册一个设备变化通知消息用来在设备状态发生变化时通知应用程序。
10)Power管理器
Power管理器负责管理系统的电源使用。它负责维护一个系统范围内的电源策略,并负责Power IRP在系统中传递路径。Power管理器通过以下几点来考虑系统应处于什么状态――浅度睡眠,深度睡眠,关闭,还是正常运作 :
w 系统当前活动状况
w 系统电池状态
w 应用程序的电源请求
w 用户动作,例如:按下电源启动按钮
w 控制面板的设置
1.3 Windows 2000和WDM 驱动程序的设计目标
WDM设备驱动程序和Windows2000在设计目标上有很多方面是一致,尤其是系统输入输出管理器更是如此。这些目标包括以下几方面:
A) 优秀的可移植性
Windows2000用以下几种方法实现在不同的硬件结构和平台上的可移植性:
w 大多数的Windows2000组件提供一组内核服务例程, 供驱动程序和其它内核组件调用。 这些服务例程提供统一的编程接口,尽管这些服务例程底层的实现因为Windows版本不同而不同,而他们的编程接口保持稳定不变,这就使得基于其上的组件和驱动程序保持平台无关性和向后兼容性。
w Windows2000采用了分层设计,依赖于处理机体系结构和平台的系统底层部分被隔离的单独模块之中,所以系统的高层可以被屏蔽在千差万别硬件平台之外。提供操作系统可移植性的两个关键组件是HAL和内核。依赖于体系结构的功能(如线程描述表的切换)在内核中实现。在相同体系结构中,与计算机硬件交互的功能在HAL中实现。
w Windows 2000主要是用可移植C语言编写――操作系统执行体、实用程序、设备驱动程序使用C语言编写,图形子系统部份和用户界面是用C++语言编写。只有那些必须和系统硬件通信的操作系统部分(如中断处理程序),和性能极度敏感(如描述表切换)的部分使用汇编语言编写。汇编语言代码不仅存在于内核及HAL之中,而且存在于执行体中的少量区域(例如实现执行体里本地过程调用机构中的一个模块),甚至存在在一些用户态代码库中。多数Windows2000组件是用C语言编写,所以这个操作系统就容易在复杂指令CPU平台和精简指令计算机CPU的平台上移植。内核设备驱动程序也应当用C语言编写,这样它也可以在系统兼容的编译器上重新编译链接直接生成不同平台的设备驱动程序。WDM驱动程序同时可以在Windows98以后的系列操作系统中保持兼容,而不用重新编写代码。
w 为保证系统的可移植性,驱动程序应避免使用平台相关的C编译器进行编译。大体上讲,驱动程序的代码尽可能用标准C编写的.驱动程序应注意以下几点:
1) 驱动程序代码应避免使用平台相关的数据结构
2) 避免调用任何保持状态的C运行时库函数
3) 应尽量使用操作系统提供的服务例程同时尽量少的使用C运行时库函数
4) 不能在驱动程序中使用浮点运算
B) 软硬件可配置性
w 避免使用机器相关的硬编码,使得同一硬件可以在其它不同配置的机器上运行,这叫做硬件可配置性。
w 底层的驱动程序要为上层的各种驱动程序服务,它不能武断地猜测上层驱动的性质和特定实现,这叫做软件可配置性。
w Windows 2000的配置管理器提供了注册登记(Registry)数据库,在其中存储了系统的硬件、各种外设及其驱动程序的信息。驱动程序可以从中得到机器的硬件配置和系统中的其它驱动信息。即插即用管理器和内核驱动程序利用注册数据库来取得本机上硬件以及其它驱动程序的配置。Win2000驱动程序也把自己的设备信息以及机器相关的配置信息储存于此。
C) 总是可以被剥夺执行权,总是可以被中断
在Windows2000中内核组件代码按照以下优先级原则进行运行:
w 内核定义的线程优先级策略
系统中各线程都有其自身的优先级属性。大体上讲儿,大多数系统线程都有可变的优先级属性,它们总是可以被剥夺的而且它们和其它优先级相同线程一起被调度。一些系统中的线程具有实时优先级,它们可以抢先其它低优先级线程运行,同时它们也被优先级更高的线程抢先。
w 内核定义的中断请求级策略
内核同时把不同的软件中断和硬件中断赋予不同的优先级,这样某些高优先级的内核代码就能够运行在较高的中断请求级,某些特定的实时代码就能拥有很高的调度优先权。通过内核代码中运行的中断请求级可以确定相应的硬件优先级。内核模式代码应该始终可以被中断,较高优先级的中断随时都有可能发生,它可以中断当前执行的代码,立即投入运行。换句话说,在一定中断请求级上运行的代码在当前处理器上可以屏蔽相同或较低优先级的中断。内核定义了一种可移植的IRQL,如果处理器具有特殊的中断相关的特性(例如,第二时钟),则可以增加IRQL。按优先级排队中断,较高优先级的中断可以抢先较低优先级的中断服务,每个处理器IRQL设置决定了处理机可以接受哪些中断,IRQL也被用于同步访问核心态数据结构,当核心态线程运行时,它可以降低或提高处理器的IRQL。如果中断源的IRQL高于当前中断设置,则它的中断可以中断处理器;如果中断源的IRQL等于或低于当前中断级,则它的中断将被封锁或屏蔽,直到一个正在执行的线程降低了IRQL。
操作系统的可以被剥夺和可以被中断的设计目标带来的好处是最大限度提高平均性能,任何中断服务例程都可以被更高中断请求级的中断服务例程中断,任何线程都可以被更高优先级的线程抢先。
D) 多处理器安全
在Windows2000多CPU操作系统平台,以下几种情况有可能发生:
在对称多处理多平台上,每个处理机都可以访问内存,发生中断,访问输入输出控制寄存器。(比较而言,在非对称多处理平台上,只有主CPU可以处理中断。)
Windows2000被设计为可以不加修改地运行在单处理器和多处理器平台上,所有驱动程序也必须安全的实现多处理器安全,避免被一个处理机访问和修改的存储器同时被另一台处理器访问和改变,比如说,一个设备的中断同时在两个处理器上发生,那么在一个处理器上特定优先级上的中断服务例程必须提供某种机制排他的访问临界区。在多处理器情况下,驱动程序的输入和输出请求可能是重叠的,这就是说,一台处理机正在处理输入请求,而另一个处理器在和设备通信,在这种情况下,所有的驱动程序必须同步对共享的资源进行访问(如果有必要的话)。内核组件提供一种叫自旋锁的机制,用来在多处理机平台上保护共享数据,使用自旋锁有以下两条原则:
w 在一个时刻只有一个例程能够获得此自旋锁,只有持有它的例程可以访问被自旋锁保护数据,其它例程必须等待这个例程是释放自旋锁,然后重新获得后,才能去接触共享的数据。
w 每一个系统中的自旋锁都有相关的中断请求级,只有运行在这个中断请求级上的例程才有可能获得自旋锁。
E) 基于面向对象的设计的思想
w Windows2000是基于面向对象的思想设计,执行体中的不同组件定义了许多对象类型,每一个组件同时提供了操纵这些对象方法,组件不允许直接操作其它组件的对象,为使用其它组件的对象,必须使用其提供的接口。严格遵循这些原则有利于增强Windows2000的可移植性和灵活性,例如,未来版本的Windows操作系统有可能部分或者全部的重新定义这些对象结构,这些被重写的对象版本将不会对现有的执行体组件产生影响,换句话说,操作系统中组件的通信服务方式也是基于接口,驱动程序同样应该避免使用引用或者指针操作对象本身。
w 和操作系统一样,驱动程序和他们的设备也是基于面向对象对象设计的,对系统的其它组件而言(包括用户代码),同设备联系被定义为一种打开文件操作,在输入输出子系统中,每一个驱动程序(逻辑,虚拟,和物理设备)都由“设备对象”来表示,每一个驱动程序加载映像都用“驱动对象”表示。输入输出管理器定义这些对象。
F) 依可重用的包(输入输出请求包,IRP,I/O Request acket)驱动的I/O
输入输出管理器的主要任务是接收输出请求(通常来自于用户应用程序),创建IRP来代表它,将这些IRP引导到正确的服务例程来处理;并且跟踪他们直到他们被完成,最后向发起者返回状态参数。输入输出管理器,插即用管理器,电源管理器使用IRP来同核心态驱动程序进行通信,并且允许驱动程序之间进行通信。因此每一个IRP都由两大部成:
w 固定部分:输出输入管理器用来保持原始输入请求信息,例如调用者线程ID和调用参数,打开文件指定的设备对象,等等。固定部分同时包含一个输入输出状态,驱动程序用于返回输入输出操作的结果。
w 可变部分:数量不同的输入输出堆栈,用于支持分层的驱动程序结构。
G) 支持异步I/O
w I/O管理器提供异步I/O支持以便I/O传输的发起者可以不必等待I/O操作的完成而继续它的工作。异步I/O提高了用户应用的性能,也提高了驱动程序的性能。异步I/O使得驱动程序不必按照I/O请求到来的顺序进行操作。这样,驱动程序内部必须维护I/O请求当前处理的阶段状态。
w 内核驱动程序处理I/O请求的次序不一定和这些I/O请求到达I/O管理器的次序相同。I/O管理器或高层驱动也可以将大的数据请求块划分为多个小块来进行处理。而且,内核驱动并不一定串行的处理这些I/O请求。这就是说,驱动程序不一定等到前一个IRP完成才去处理下一个IRP。
2 WDM驱动程序和I/O子系统的协同工作机制分析
在Windows驱动程序模型(WDM)出现之前Microsoft为它的两个系列操作系统提供了不同的驱动程序模型,他们分别是:Windows3.1和Windows95的Vxd以及NT式的驱动程序。两种驱动程序的架构是如此的不同,以至于程序员必须为每个设备在两种系统上分别编写、编译、调试驱动代码,这对于程序员来说是一个很大的负担。
为统一驱动程序架构,减轻程序员的负担,微软推出了新型的WDM驱动程序模型,现在的Windows 98(第二版)以及Windows 2000均采用了这个模型。而且微软已经宣布未来的Windows系列操作系统的驱动模型将是基于WDM构架的。
Windows驱动程序模型(WDM)的设计思想是非常先进的,它迎合了当前的高级操作系统的设计的先进之处:
w 它是支持多处理器架构的,完全支持对称多处理
w 它是处理器无关的,支持多种处理器架构。
w 它完全支持即插即用和电源管理
尽管对于用户来说Windows 98 and Windows 2000下的模型是相同的,但是他们的工作机制是完全不同的,以下将分别讨论。
2.1 Windows2000下的WDM驱动程序和I/O子系统的协同工作机制分析
图2 Windows 2000 I/O请求简化模型
如图2所示,Windows 2000是分态的操作系统,因此在系统中代码运行在两种模式下:用户态和核心态。用户应用程序运行在用户态,操作系统代码(如系统服务和设备驱动程序)在核心态下运行。在核心态处理器模式中,代码可以访问所有系统内存和所有的CPU指令。操作系统软件的特权级别高于应用程序软件,通过这种机制,使得应用程序的不当行为在总体上不会破坏系统的稳定性。Windows 2000对核心态运行的组件不提供任何保护。换句话说,一旦处于核心态,系统代码就可以完全访问系统内存空间,并在访问时不受安全性的约束。
Win32 应用程序运行在用户态,操作系统严格限制应用程序的行为,防止其对其它应用和系统代码的破坏。因此用户态程序只能调用Win32子系统提供的API来同设备交互,Win32子系统模块中的服务代码然后调用平台无关的系统服务例程来切入核心态并调用核心态组件(I/O管理器)提供的服务例程。当请求传递到I/O管理器时,它创建一个IRP(I/O Request acket)并将其传递到适当的驱动程序去,并给应用程序一个消息,通知这次I/O操作还没完成。应用程序收到通知后或者继续执行,或者挂起等待。在任何一种情况下驱动程序独立的执行来服务应用程序。
驱动程序最终需要访问硬件来完成I/O请求,在程序控制的I/O方式下的读请求,驱动程序的动作为读I/O端口或寄存器。尽管驱动程序运行在核心态下,可以直接和硬件交互,但在大多数情况下,为保持其可移植性,驱动程序一般调用硬件抽象层提供的例程来和硬件交互。硬件抽象层提供一种与处理器平台无关的方法来执行实际的I/O 操作。例如,在Intel x86处理器平台上,硬件抽象层使用IN指令,在Alpha平台上,它将使用MEMORY读取操作。
驱动程序完成I/O操作后,它将调用一个特殊的内核服务例程来完成IRP。这时,因这个I/O请求挂起的任何Win32应用将继续执行。
2.2 Windows 98下的WDM驱动程序和I/O子系统的协同工作机制分析
在Windows98中,操作系统内核被称为虚拟机管理器(VMM),因为它的主要任务是在单一的物理硬件上创建一个或多个“虚拟”的机器来共享这些硬件。而虚拟设备驱动程序(VxD)的设计初衷也是虚拟一个特定的硬件来辅助虚拟机管理器产生一种假象,那就是:每一个虚拟机都拥有一套完整的硬件。这种设计源于Windows 3.0,并在Windows 95和Windows 98中沿用。
Windows98并不象Windows 2000那样统一有序的处理I/O请求。Windows98在处理磁盘、串口、键盘等等这些不同的设备时采用的机制有很大不同。并且如图3所示,Windows98在处理32位和16位应用的I/O请求时,采用的方法也是根本不同的。图3中的左列展示了系统是如何处理32位应用的I/O请求的。在Windows98中,对于不同的设备,应用程序采用不同的机制来和驱动程序进行交互。应用程序可能调用一个Win32 API例如ReadFile来读设备,但这些设备仅包括磁盘文件,串口,和一些由WDM驱动程序驱动的设备。但对于其它设备,应用程序只能通过基于DeviceIoControl的一些特别的机制来和设备通讯。即使是同样调用ReadFile,应用程序使用一种机制来和磁盘驱动交互,采用另一种机制和串口驱动通讯。而由WDM驱动的设备的I/O请求方法和前两种又有本质的不同。
图3的中列和右列展示了系统是如何处理16位(Windows 3.1)和DOS应用程序的I/O请求的。在两种情况下,应用程序都直接或间接的和用户态驱动程序进行交互,用户态驱动程序将请求传递到核心态虚拟设备驱动,再由核心态虚拟设备驱动代理应用程序和硬件直接交互。
虽然在Windows 2000中以一种统一的方式(IRP)在内核中传递I/O操作,但在Windows 98中,即使在核心态也没有统一方法的来代表I/O请求。
但对于WDM驱动程序来说,Windows 98在处理方式上通过一个系统模块(NTKERN.VXD)来模仿Windwos 2000内核的处理方法。这个模块提供了Windows 2000内核的大部分服务例程的仿真,并模拟I/O管理器来产生和发送IRP。WDM驱动程序几乎无法区分两种平台的差异。
图3 Windows98 I/O请求简化模型
3 分层的设备驱动程序和即插即用设备栈
3.1 分层的设备驱动程序
图4说明用户的动作最后如何由设备驱动程序处理。应用程序对设备I/O进行Win32调用,这个调用由I/O系统接收。I/O 管理器发送IRP来请求驱动程序的处理。
在最简单的情况下,I/O管理器只是把IRP传递给一个设备驱动程序,这个设备驱动程序和硬件交互,并完成IRP的处理。I/O管理器把数据和结果返回给Win32和用户应用程序。
通常IRP由分层的驱动程序栈来处理。高层的驱动程序把请求划分成更简单的请求并传递给下层驱动程序。例如,在文件系统驱动程序中,最高层的驱动程序知道文件如何在磁盘上表示,但不知道如何得到数据的细节。中间层次的驱动程序进一步处理请求,将一个IRP中的请求划分为若干个小的请求并传给下层驱动程序。最后,最低层的驱动程序与硬件打交道。
在任何地的地方,驱动程序被设计为尽可能的通用。例如,SCSI端口知道如何把磁盘数据请求转换成SCSI请求,但是,它再向SCSI小端口驱动程序发出SCSI请求,这些小端口驱动程序知道如何访问各种SCSI硬件。
过滤驱动程序是一种中间驱动程序,它位于其它的驱动程序层次之间,提供一些附加的功能,而不影响其它驱动程序。例如,过滤驱动程序可以用以提供容错的磁盘访问,在这个驱动程序中,数据被写到两个不同的物理磁盘,保证数据的冗余保存。
图4 驱动程序分层简化模型
3.2 即插即用设备栈
Windows驱动程序模型重新定义驱动程序分层,以适用于即插即用系统。一个设备栈代表处理请求的驱动程序层次,如图5所示。总线驱动程序控制对总线上的所有设备的访问。例如,如果访问USB设备,必须使用USB总线驱动程序。
总线驱动程序负责枚举它的总线,这意味着发现总线上的全部设备和检测设备何时被添加或删除。总线驱动程序创建一个物理设备对象来代表它发现的设备。一些总线驱动程序简单的控制对总线的访问,一旦有了控制权,就可以对总线做任何想要的工作。在其它情况下,总线驱动程序为我们处理总线上的所有事务。
功能驱动程序知道如何控制设备的主要功能,它分层在总线驱动程序的上面。功能驱动程序创建一个功能设备对象,放在设备栈中,在USB的情况下,功能驱动程序必须使用USB类驱动程序访问它的设备。但是,在其余情况下,一旦总线驱动程序接管,功能驱动程序可以直接访问硬件。有多个功能驱动程序分层在第一个功能驱动程序之上是非常可能的。
各种类型的过滤驱动程序可以插在设备栈中。对总线上的所有设备,总线过滤驱动程序被加在总线驱动程序之上;而对于特定类的所有功能驱动程序,添加类过滤驱动程序。设备过滤驱动程序仅对特定的设备添加。上层的过滤驱动程序在功能驱动程序之上,而底层过滤驱动程序在功能驱动程序之下。
用户的请求总是在设备栈的顶部进入。假设用户程序标志了一个它想访问的功能设备,I/O管理器保证它的全部请求都发送到设备栈的顶部,这样任何高层的过滤驱动程序或功能驱动程序首先得到处理这些请求的机会。
图5 典型的即插即用设备栈
3.3 标准总线驱动程序和类驱动程序
图6给出了Windows提供的主要类驱动程序和总线驱动程序,它们是作为总线驱动程序和功能驱动程序执行的通用驱动程序。在大多数情况下,主系统驱动程序和与硬件接口的另一个驱动程序或另一类驱动程序一起使用。这些辅助驱动程序通常成为小驱动程序。
图6 总线驱动程序和类驱动程序分类
高级配置和电源接口(ACPI)总线驱动程序与PC ACPI BIOS打交道,枚举系统中的设备并控制它们的功率使用。PCI总线驱动程序枚举和配置PCI总线上的设备。PnPISA总线驱动程序对可以使用即插即用配置的ISA设备作类似的工组。
流类驱动程序提供访问高带宽,时间关键和视频,音频数据的基础。
IEEE 1394枚举和控制IEEE 1394高速总线,这个总线驱动程序使用端口驱动程序访问IEEE 1394控制电路。IEEE 1394客户驱动程序发出IEEE 1394请求块来控制它们的设备。
USB枚举和控制低速的USB总线。主机控制器驱动程序作为访问主要的两类USB主机控制器的标准。
SCSI和CDROM驱动程序用于访问硬盘,软盘,光盘和DVD。
人工输入设备类设备驱动程序提供输入设备的一个抽象视图。
4 WDM驱动程序的结构及主要例程
WDM驱动程序的主要工作如下:
w 初始化自己。
w 创建和删除设备。
w 处理Win32打开和关闭文件句柄的请求。
w 串行化对设备的访问。
w 访问硬件。
w 调用其它驱动程序。
w 取消I/O请求。
w 超时I/O请求。
w 处理一个可热插拔的设备被加入或删除的情况。
w 处理电源管理请求。
以下分别分析了完成以上功能的WDM驱动程序模块和工作原理:
A) WDM驱动程序入口点和回调例程
WDM驱动程序有两个主要的初始化入口点---- DrvierEntry和AddDevice例程。DrvierEntry注册其它回调例程的。在运行过程中,内核会调用不同的回调例程来完成不同的任务。
下表列出了主要的回调例程:
DriverEntry 初始驱动程序入口点,设置主要的回调例程。
AddDevice 一个新的即插即用设备(PnP)被添加。
I/O请求包处理例程 被调用用来处理希望处理的IRP
Unload 卸载驱动程序
StartIo 串行处理IRP的回调例程
ISR 中断服务例程
DpcForIsr 延时过程调用例程,完成一个I/O请求和开始另一个中断驱动的传输
临界断例程 同步一个处理器上的中断执行。由低级IRQL的任务调用来与硬件交互
Cancel 取消一个IRP
Completion 当一个低层驱动程序完成一个IRP的处理时被调用,这允许当前的驱动程序作更多的工作
AdapterControl 当一个DMA通道适配器可用时调用
ControllerControl 当一个控制器空闲时调用
Timer 定时器回调例程
CustomerTimerDpc TimerDpc的回调例程
CustomerDpc 通常用于处理工作队列
即插即用通知 当设备PnP状态发生变化时被调用
电源通知 当设备Power状态发生变化时被调用
ConfigCallBack 查询设备硬件描述回调
B) I/O系统服务派发(Dispatch)例程
驱动程序的DriverEntry例程必须设置一系列的回调例程来处理IRP。下表列出了常见的Win32设备I/O函数和它们对应的IRP:
CreateFile “Create” IRP
CloseHandle “Close” IRP
ReadFile “Read” IRP
WriteFile “Write” IRP
DeviceIoControl “IOCTL” IRP
内部的IOCTL IRP
“Create”, “Close”, “Read”, “Write”, “IOCTL”和内部的IOCTL的处理程序通常称为分发例程,因为它们通常仅执行IRP的一些初始化处理,如检查所有的参数是否合法,然后把IRP分发到驱动程序的其它地方去处理。IRP通常需要串行处理,使得驱动程序以一种安全的方式与硬件打交道。
5 WDM驱动程序的工作流程分析
A) 创建设备
设备的创建一般在即插即用管理器发现设备时进行。类似的,在执行过程中,当驱动程序卸载或者PnP管理器告诉设备正在被删除时,我们要删除这些设备对象。
大多数的WDM设备对象都是在PnP管理器调用AddDevice入口点时创建的。这个例程在插入新设备和安装INF文件指示这个驱动程序是要运行的驱动程序时被调用。在此之后,一系列的PnP IRP被发送到驱动程序,指示设备应当何时启动和查询它的功能。最后,一个删除设备PnP IRP指示设备已经被删除,所以驱动程序删除设备对象。
NT式驱动程序在它们想创建设备的时候创建设备。它们的DriverEntry例程通常寻找自己的硬件,找到后创建设备对象。
在创建设备后,为了使Win32可见,我们必须为每个设备创建符号链接。可以采用两种方法创建符号链接:第一种方法是采用显示的“硬编码”符号链接名,
用户态程序必须类似的把设备名硬编码到源代码中。另外一种方法是使用设备接口,每个设备接口由一个全局唯一标志符标志。把设备注册为一个特定的设备接口就创建了一个符号链接。用户态设备可以取得拥有此GUID的设备。
B) 硬件资源分配
低层的驱动程序需要知道为他们分配了哪些硬件资源。最常见的硬件资源是I/O端口,存储器地址,中断和DMA线。
处理PnP IRP的WDM驱动程序在收到“启动设备”的PnP IRP时被告知设备的资源。NT式的驱动程序必须自己发现资源并请求使用这些资源。
C) 驱动程序的分层调用
WDM驱动程序花大量的时间访问其它驱动程序。一个即插即用设备是在一个设备对象栈中,把IRP沿设备栈传递到下一个驱动程序是很常见的。
一些类型的IRP(如即插即用,电源管理等)常常立即传递到设备栈中的下一个设备。
在另一些情况下,驱动程序的工作是通过调用设备栈中的下一个设备实现的。例如,USB客户驱动程序通常通过沿设备栈向下传递IRP来调用USB总线驱动程序。
D) 串行化处理
访问硬件的任何设备必须使用某种机制保证驱动程序的不同部分不同时访问相同的硬件。在一个多处理器系统中,“Write”IRP处理程序可以同时在两个不同的处理器上运行。如果它们两个都试图访问相同的硬件,则会出现不可预料的结果。同样,如果一个“Write”IRP正在试图访问硬件的同时发生了中断,那么,两个动作可能会相互影响。
内核采用两种机制来同步这些冲突操作:
w 第一种是采用临界段例程,使用这些临界段例程保证代码不会被中断处理程序中断。这些临阶段例程在内部使用了中断自旋锁,所以可以保证多处理器同步。
w 第二种是使用StartIo例程串行处理IRP,每个设备对象有一内部的IRP队列,驱动程序的派发例程将IRP插入这个队列中。内核I/O管理器从这个队列一个个的取出IRP,并把它们传递到驱动程序的StartIo例程。所以StartIo例程串行的处理IRP,保证不与其它的IRP处理例程冲突。
如果一个IRP已经在一个队列中,此时用户线程突然中止或其调用Win32函数取消了这次I/O,驱动程序必须取消这个IRP。这可以通过给每一个排队的IRP挂接一个取消回调例程来实现。
如果用户态程序关闭了设备的文件句柄,而这个设备有重叠请求在等待,则必须要有“清理”例程。清理例程负责取消与一个文件句柄关联的所有IRP。
E) 访问硬件
在取得了I/O端口和内存地址后,访问硬件就显得比较直接,但是由于Windows 是多任务系统,在一般情况下占用处理器的时间应小于50微秒,需要长时间的硬件访问应采用系统线程的方法。
因为中断服务例程的IRQL(中断请求级)很高,故在中断服务例程中应尽量减少访问硬件时间,而且很多内核例程在这个中断请求级上被禁止调用,故一些中断善后工作在延迟过程调用(DPC)中实现。
F) 即插即用支持技术
即插即用(PnP)是计算机系统自动识别和适应硬件配置的改变的技术,完全的即插即用不仅需要硬件的支持,同时也需要软件的支持。对用户来说,他可以自由的向计算机中添加和删除设备,而无需使用笨拙的手工配置同时不必深入了解复杂的计算机软硬件知识。
即插即用需要设备硬件,系统软件,和驱动程序的支持。PnP的发起者定义了关于易识别设备和基本系统组件的PnP工业标准。这些标准主要阐述了系统软件和驱动程序如何支持即插即用。
系统软件和驱动程序对即插即用的支持主要提供以下功能:
w 自动识别已安装的设备
w 硬件资源的动态重分配
w 自动加载正确的驱动程序
w 提供使得在硬件环境发生变化时,驱动程序和用户模式代码得到通知的机制
PnP驱动程序是对PnP支持很重要的部分。PnP驱动程序必须提供PnP调用入口,处理繁多的PnP IRPs,并且遵循PnP的设计规范。
图7说明了操作系统的组件如何同WDM驱动程序一起提供即插即用的支持。从图7可以看出PnP管理器由两部分组成:核心态PnP管理器和用户态PnP管理器。核心态PnP管理器负责同操作系统组件和WDM驱动程序交互来配置、管理和维护设备。用户态PnP管理器主要和用户态安装程序一起来简化设备的安装工作。同时,PnP管理器只有和WDM驱动程序紧密合作,才能使得系统对即插即用有完整的支持。WDM驱动程序遵循Windows 驱动模型规范并且可以不加修改的同时运行在Windows2000 和Windows98中。为支持PnP,WDM驱动程序的设计必须遵守以下原则:
w WDM驱动程序必须遵循以设备为中心的即插即用驱动程序模型。PnP管理器在全局的基础上管理系统的设备,并调用驱动程序来服务它们的设备。当PnP管理器发现设备时,驱动程序的AddDevice例程将被PnP管理器调用来服务这个设备。
w WDM驱动程序必须遵守PnP软件设计原则例如:驱动程序必须拥有专门的例程来处理繁多PnP IRP并正确传递这些PnP IRP,这是一项非常繁琐复杂的工作。
w WDM驱动程序不应自已声明硬件资源,此项工作应由PnP管理器在系统范围公断并调整资源后完成。
w WDM驱动程序应该是模块化的,驱动程序的各个例程都要求有独立的入口和模块以方便PnP管理器在需要时调用。
为配合PnP管理器的工作,WDM驱动程序必须自己维护多个PnP状态。在PnP系统中,当设备被配置、启动、停止时设备会在不同的PnP状态之间转换,如图8所示。
正确的维护本设备的PnP状态和响应相应的PnP IRP的状态迁移指示是WDM驱动程序对PnP支持的关键。
图7 WDM驱动程序中的PnP组件
G) 电源管理支持技术
电源管理是在系统范围内的有效使用电源的综合方法。拥有硬软件电源管理支持的计算机系统能够拥有以下功能:
w 最短的启动和关机延迟。
w 极大提高的整体电源使用效率和更长的电池寿命。
w 更安静的操作。
在Windows2000和Windows98以及其它支持电源管理的操作系统中,计算机和它的外设被维持在可能的最低电源使用水平上来完成它们当前的工作。WDM驱动程序和操作系统合作来管理它们的设备电源。如果所有的驱动程序都支持电源管理,那么操作系统就可以在全局的基础上维持系统的电源消耗,来节约电量的使用,更快的启动和关机,而且可以随时被从睡眠中唤醒。
支持PnP的驱动程序必须支持电源管理,反之亦然。电源管理和即插即用是综合在一起的且是相互依赖的。
电源管理在两个层次上工作,第一层在独立的WDM驱动程序中,第二层在以电源管理器为核心的系统整体范围内。
图8 设备的PnP状态迁移图
电源管理器作为操作系统内核的一部分在系统范围内管理电源水平。所有的驱动程序都支持电源管理,电源管理器就可以在系统范围内统一管理电源的消耗,不仅使系统在适当的时候进入完全开启和完全关闭状态,而且可以在几个不同的中间状态中进行睡眠。
设备电源管理应用于单独的设备。支持电源管理的驱动程序可以将它的设备在使用时加电,并且在不用时掉电。如果硬件支持,则设备可以在驱动程序的控制下进入中间的设备电源状态。
电源管理和即插即用一样同样需要硬件,系统软件和驱动程序的共同支持。硬件的支持在“高级配置与电源管理接口规范”(ACPI 参考http://www.teleport.com/~acpi2))中有详细说明。WDM驱动程序和电源管理器从软件上维护设备及系统的电源状态。图9说明了WDM驱动程序如何和电源管理器合作进行电源管理的。
用户可以通过控制面板和系统API来向电源管理器输入他们的电源命令。电源管理器管理系统范围内的电源策略----支配系统电源使用的规则。通过从控制面板和系统API得到信息,电源管理器可以决定应用程序正在或者可能需要使用不同的设备,从而适当的调整系统的电源策略。
当电源管理器请求改变系统电源状态时,驱动程序应当响应请求并把它们的设备投入到对应的设备电源状态。
电源状态指明了电源消耗的水平。电源管理器设置系统电源状态,驱动程序设置设备电源状态。WDM驱动程序定义了同ACPI说明相同的五种系统的电源状态(从S0到S5)和四种设备电源状态(从D0到D5)。同即插即用的支持相同,正确的维护本设备的电源状态和响应相应的Power IRP的电源状态迁移指示是WDM驱动程序对电源管理支持的关键。
图9 WDM电源管理组件
6 小结
WDM设备驱动程序工作在Windows2000或Windows98的内核状态,和系统的其它核心部件打交道。理解Windows2000的设计目标、实现及其结构对编制合格的驱动程序至关重要。
本文首先介绍了Windows2000操作系统的设计目标、特点,同时着重介绍了内核模式的组成和各构件的功能。作为系统重要组成部分的WDM驱动程序在很多方面和操作系统的设计目标是一致的。本文第二节简要地介绍了WDM驱动程序的分类以及驱动程序的特点、设计目标。由于WDM驱动程序工作在Windows2000和Windows98下时和I/O子系统的交互的差异(由于操作系统设计的差别),本文分别讨论了WDM驱动程序在不同操作系统下的工作模式。指出了它们的异同点。WDM驱动程序的即插即用设备栈和标准总线驱动程序也是本文讨论的内容之一。在本文的最后,本文给出了WDM驱动程序的结构和主要例程,并分析了WDM驱动程序的工作原理。
从本文中可以看出,要编制WDM设备驱动程序必须掌握大量的预备知识,理解和领会各种复杂的内核例程和许多繁杂的驱动程序开发原则,这就带来了本文开始时讨论过的弊病:需要专门的人力投入,开发周期长,正确性、可靠性难以保证,升级困难,可读性差等。
如何更高层次上利用DDK,实现通用部分的封装(如资源查找和分配,设备物理内存及I/O空间到NT虚拟空间的映射),则可以减少重复开发的工作量,正确性和可靠性更容易得到保障。在此基础上,利用设备的各种自有特征,自动生成驱动程序成为可能,使驱动程序的开发更为简化,开发周期更短。集成化的开发环境是高效编程的保证之一,恰恰是DDK所欠缺的,在研究中将力图解决这个问题。