即插即用和电源管理的介绍 本章描述了与编写支持即插即用和电源管理的驱动程序相关的主要概念,这些信息如下组织: 1.1 什么是即插即用? 1.1.1 PnP组件 1.1.2 PnP的支持级别 1.2 什么是电源管理? 1.2.1 电源管理的最初含义 1.2.2 电源管理的支持级别 1.2.3 系统范围的电源管理总览 1.2.4 电源状态 1.3 设备树 1.4 驱动程序分层和设备对象 1.4.1 驱动程序种类 1.4.1.1 总线驱动程序 1.4.1.2 功能驱动程序 1.4.1.3 过滤器驱动程序 1.4.2 驱动程序分层---一个例子 1.4.3 设备对象种类 1.4.4 设备对象---一个例子 阅读第2章对驱动程序怎样支持PnP和电源管理有一总体印象。阅读第2部分获取关于PnP的详细信息,阅读第3部分获取关于电源管理的详细信息,阅读第4部分获取关于驱动程序和设备安装的详细信息。 1.1 什么是即插即用? 即插即用(PnP)是一个硬件和软件支持的组合,这些软件支持能让系统识别或适应硬件配置的小改变而没用用户的干预。用户可从计算机系统添加或删除设备而不必做笨拙而且混淆的手工配置,也不需要复杂的计算机硬件知识。例如,用户可以连接一便携机并使用连接站键盘、鼠标和监视器而不需要手工改变配置。 PnP需要从硬件设备、系统软件和驱动程序获得支持。硬件工业的最初思想是给容易标识的插件板和基本的系统组件定义标准(比如PnP ISA定义和PC Card标准)。这本DDK文档编制集中在系统软件对PnP的支持和驱动程序怎么使用这个支持来实现PnP。 系统软件对PnP的支持和PnP驱动程序一起提供以下功能: n 对已安装硬件的自动和动态识别 在最初的系统安装过程中系统软件识别硬件,识别发生在系统引导之间的PnP硬件改变,并对诸如连接或断开的运行时间硬件事件和设备添加或删除作出响应。 n 硬件资源分配(和再分配) PnP管理器决定由每一设备(例如,输入/输出[I/O]端口、中断请求[IRQ]、直接内存访问[DMA]通道和内存定位)所请求的硬件资源,并适当分配硬件资源。当需要时,PnP管理器重新配置资源分配。例如当新设备被添加到必须正在使用的资源系统中。 PnP设备驱动程序不分配资源,相反,当一个设备被枚举时,设备所请求的资源是被标识过的。在资源分配过程中,PnP管理器找到每个驱动程序的请求。对一个早期的设备而言,资源不是动态配置的,所以,PnP管理器首先给早期的驱动程序分配资源。 n 适当的驱动程序的加载 PnP管理器决定哪一些驱动程序对支持每个设备是必要的,并加载这些驱动程序。 n 驱动程序与PnP系统相互作用的编程接口 接口包括IoXxx例程、PnP IRP、必要的驱动程序入口点和注册表信息。 n 驱动程序和应用程序了解硬件环境变化并采取适当的动作机制 PnP能让驱动程序和用户模式代码登记,并通知一定的硬件事件。 PnP驱动程序是PnP支持的重要部分,作为一个合格的PnP驱动程序必须提供必要的PnP入口点、处理必要的PnP IRP和遵从PnP向导行。欲知更多信息,参见第2章。 1.1.1 PnP组件 图1.1显示了一组同时工作来支持PnP的组件 图1.1 PnP软件组件 PnP管理器有两个部分:内核模式PnP管理器和用户模式PnP管理器。内核模式PnP管理器与OS组件和驱动程序相互作用来配置、管理和维护设备。用户模式PnP管理器与用户安装组件相互作用来配置和安装设备,例如类安装器。用户模式PnP管理器也与一些应用程序相互作用,举例来说,登记一个设备改变通知的应用程序,并当一个设备事件发生时通知应用程序。 PnP驱动程序支持物理的、逻辑的和虚拟的机器设备,WDM驱动程序遵从微软的驱动程序模式并支持微软的Windows2000和Windows98系统上的设备。WDM PnP驱动程序通过严格定义的API和IRP与PnP管理器和其他内核组件通信。参看《Windows2000驱动程序开发参考》卷一获得这些API和IRP的详尽描述。安装了Windows2000的机器也许有一些PnP驱动程序不支持WDM。 所有PnP驱动程序应该支持PnP和电源管理,如果一单个PnP的驱动程序不支持PnP和电源管理,它作为一个系统整体约束PnP驱动程序和电源管理。 参考第4部分关于设备和驱动程序安装的信息,包括INF文件、.cat文件和注册表。 1.1.2 PnP的支持级别 设备支持PnP的范围取决于设备硬件和设备驱动程序里的PnP支持(看表1.1)。 表1.1 对驱动程序/设备综合的PnP能力级 PnP驱动程序 非PnP驱动程序 PnP设备 完全PnP 无PnP 非PnP设备 可能的部分PnP 无PnP 任何支持PnP的设备在其驱动程序里都应该有PnP支持。 如果一个非PnP设备被一个PnP驱动程序驱动的话,它能够有一些PnP能力。举例来说,一个ISA声卡和一个EISA网卡可手工安装,然后PnP驱动程序能象一个PnP设备一样对待该卡。 如果一驱动程序不支持PnP,它的设备表现为非PnP设备而不管任何硬件PnP支持。一个非PnP驱动程序能够约束整个系统的PnP和电源管理能力。 在OS支持PnP之前编写的早期驱动程序继续如从前一样工作,但没有任何PnP能力。新的驱动程序应该包括PnP支持。 1.2 什么是电源管理? 电源管理是系统范围的、集成的方法来使用和保存电源,包括电源管理的软硬件支持的计算机系统提供以下功能: n 最小的启动和关机延迟 系统能在低电力状态时休眠,在这种状态下无须完全的重新引导系统就能恢复操作。从用户的角度来看,唤醒一休眠的计算机大致可象开关电视机一样。 n 更强大的电源效率和电池生命期 仅仅当设备传输功能给用户时电源才启用于设备,如果一个设备未使用,按照命令,电源可被断开,而后再加电。 n 更安静的操作 断开未被使用的设备电源可降低噪音,此外,硬件和软件能够管理电流装载和热装载,其结果是计算机休眠时几乎听不到声音。 工业界OnNow Initiative定义了电源管理的硬件和软件需求,细节参看电源管理的工业萌芽。 在Windows2000、Windows98和其他支持电源管理的操作系统里,计算机和其他外部设备被维持在完成手头任务所需的最低可行的电平上,驱动程序与操作系统协作来管理它们的设备电源。如果所有的驱动程序支持电源管理,操作系统能够在系统范围基础上管理电力消耗,这样,电力保存、电力切断和快速恢复,及需要时再次唤醒都可被管理。 这些集成的电源管理方法---包括在操作系统、系统硬件、设备驱动程序和设备硬件里,产生如下的结果: n 更智能的电源管理决定。在每一电平,被最优通知的组件指导电力使用。 n 更高的可靠性。更好的电源管理决定减少了不适当的时间关机和数据丢失的可能。 n 平台的独立性。操作系统在不同的硬件平台之间使用一种可控制的、单独的方法来管理电源,允许电力在不同的设备上最大保存。 n 更好的设备集成。符合工业界规范的设备驱动程序确保最大的电力保存而不管其硬件平台。 综合而言,这些优点导致保存更多电能和更有效地利用电能,这在以前是不可能的。 1.2.1 电源管理的最初含义 OnNow Initiative描述了电源管理所需的硬件和软件支持。 高级配置和电源管理接口规范的OnNow Initiative部分,描述了一个硬件级接口,该接口能让操作系统以一种一致的、平台独立的方式实现电源管理。 设备类电源管理参考规范对每个普通的设备类是可行的,如声频或通信设备。每一个规范描述了一个设备类的电源管理要求。驱动程序的编写者应该参考这些规范,也可通过微软的网站来获得设备相关的细节。 1.2.2 电源管理的支持级别 支持PnP的驱动程序必须支持电源管理,支持电源管理的驱动程序也必须支持PnP,电源管理和PnP是被集成而互不独立的。 电源管理有两个工作层次:整个系统和单个设备。 电源管理器,是操作系统内核的一部分,管理着整个系统的电源级别。如果系统中所有的驱动程序都支持电源管理,电源管理器能在系统范围的基础上管理电源消耗,不只利用全开或全关的状态,也有多种中间的系统休眠状态。 在操作系统支持支持电源管理之前编写的早期驱动程序能如从前一样继续工作。然而,包括早期驱动程序的系统不能够进入任何中间的系统休眠状态;它们只能象过去一样在全开或全关的状态下操作。 驱动程序处理设备电源管理。一个支持电源管理的驱动程序能够在需要时打开它的设备,不用时关闭设备。如果设备有硬件能力允许这么做,设备能够进入中间的电源状态。当前系统中早期的驱动程序没有影响更新的驱动程序管理它们设备电源的能力。 1.2.3 全系统范围的电源管理的总览 电源管理需要得到系统、设备硬件和系统软件、驱动程序的支持。如在“电源管理的最初含义”部分描述的一样,必要的硬件支持包括在工业规范里。这一部分包括了软件支持,特别地,驱动程序该做什么来遵照操作系统的要求,并正常的管理它们的设备电源。 图1.2 给出了全系统范围的电源管理总览 分别通过API和控制面板,应用程序和用户已经分别地输入了电源管理决定。用户可通过控制面板设置一定的电源选项,包括设备专用的GUID控制选项,这个选项是如果响应的驱动程序支持它时,用户界面将会提供。通过WMI,控制面板通知电源管理器和驱动程序GUID控制里的改变。 电源管理器管理全系统的电源策略,这些策略包括管理系统电源使用的规则。(细节参见第3章第3部分的系统电源管理。)使用从控制面板和得到的信息,电源管理器能够决定什么时候应用程序在使用,或者可能需要使用不同的设备,这样它能够适当调整系统的电源策略。 电源管理器也提供一个驱动程序接口,该接口主要包括PoXxx例程、电源管理IRP和必要的驱动程序入口点。 当电源管理器请求系统电源状态的变化时,驱动程序通过放置它们设备在适当的设备电源状态来响应。此外,驱动程序能发现是否设备闲置,并设置未用的设备处于休眠状态。总线专用的机制报告设备电源能力、设置并报告设备状态及改变设备电源。怎样和何时确切地改变设备电源取决于设备类型和设备硬件的能力。 尽管ACPI硬件意识到了最大地电力储存,该硬件不需要为在驱动程序里生效的电源管理而遵从ACPI规范。 1.2.4 电源状态 电源状态指示了系统或单个设备的电平消耗,也指示了枚举活动的范围。电源管理器设定整个系统的电源状态,而设备驱动程序设置它们单个设备的电源状态。 ACPI规范定义了系统和单个设备的不连续电源状态。系统电源状态被引用为Sx,这里x是0到5之间的一个状态数,设备状态被引用为Dx,这里x是0到3之间的一个状态数。状态数与电力消耗呈反相关:越高的状态数使用越少的电力,状态S0和D0是最高的电力,也是最具功能的,是最完全的状态,状态S5和D3是最低的电力状态并有最长的唤醒时间。 严格定义的电源状态允许不同制造商的许多设备能一致地、可预测地一起工作。举例来说,当电源管理器设置系统为S3状态时,其能依靠支持电源管理器的驱动程序设置它们设备相应的设备状态,并能以一种可测的形式返回工作状态。 1.1 设备树 OS维持一个能保留系统里的设备踪迹的设备树。图1.3给出了系统配置的设备树的一个例子。 设备树保留着存在于系统中的设备信息,当机器引导时,通过使用从驱动程序和其他组件获得的信息,OS构建此树,并当设备被添加或删除时更新此树。设备树是分级的,总线上的设备代表着总线适配器或控制器的“子级”。你可参看设备管理器的使用来了解设备树的设备分级(选择“View”,然后选择“Devices by connection”)。 设备树的每一个节点是一个设备节点(devnode),一个devnode包括设备驱动程序的设备对象加上由OS所保留的内部信息。参看1.4.3部分来获取更多的信息。 图1.3PnP设备树例子 设备树的分级反映了机器里设备附着的结构,当OS管理设备时它使用该分级。例如,如果一用户请求从图1.3所示机器里拔掉USB控制器,PnP管理器从设备树上决定该行为也将导致另外的三个设备被拔掉(USB集线器、操纵杆和镜头)。当PnP管理器就USB控制器询问驱动程序删除控制器是否安全时,PnP管理器同样地就控制器(USB集线器、操纵杆和镜头)的任何子代而查询驱动程序。 除过在设备树里给出的分级关系外,机器设备之间有其他的关系,这里包括删除和抽出的关系。为了更多的信息,参见《Windows2000驱动程序开发参考》的卷一参考页里的IRP_MN_QUERY_DEVICE_RELATIONS。 1.3 驱动程序层次和设备对象 I/O系统提供一个分层的驱动程序体系结构,这一部分从支持PnP和电源管理的驱动程序的角度讨论了体系结构,讨论包括各种驱动程序、驱动程序层次和设备对象。 1.3.1 驱动程序种类 从PnP的角度,有三种驱动程序: n 总线驱动程序---驱动I/O总线并提供设备间独立的每个插槽功能 n 功能驱动程序---驱动一个单个的设备 n 过滤器驱动程序---过滤一个设备、设备类或总线的I/O请求 在本书中,总线是物理的、逻辑的或虚拟的设备所能附着的任何设备;总线包括传统的总线,如SCSI和PCI,也有并口、串口和i8042端口。 对一个驱动程序编程人员来说,知道不同的PnP驱动程序种类和他们正在编写哪一类驱动程序是重要的。例如,是否一个驱动程序处理每个PnP IRP和怎样处理这个IRP取决于正在编写哪一类的驱动程序(一个总线驱动程序、功能驱动程序还是过滤器驱动程序)。 图1.4 给出了一个设备的总线驱动程序、功能驱动程序和过滤器驱动程序之间的关系。 每个设备典型地有一个父I/O总线的总线驱动程序管理,一个设备的功能驱动程序,零个或多个设备的过滤器驱动程序。需要许多过滤器驱动程序的驱动程序设计不能产生最佳的执行。 图1.4里的驱动程序如下: 1. 总线驱动程序服务于一个总线控制器、适配器和电桥。总线驱动程序是必须的驱动程序;在一个机器上,每个类型的总线有一个驱动程序。微软为大多数通用总线提供了总线驱动程序,IHV和OEM则提供了其他的总线驱动程序。 2. 总线过滤器驱动程序典型地给一总线增加值,它由微软或系统OEM提供,对一个总线来说,可以有任何数目的总线过滤器驱动程序。 3. 低层过滤器驱动程序典型地修改了设备硬件的行为,它们是可选择的且典型地由IHV提供,对一个设备来说,可以有任何数目的低层过滤器驱动程序。 图1.4 可能的驱动程序层次 4. 功能驱动程序是主要的设备驱动程序,一个功能驱动程序典型地由且必须由设备商来编写(除非设备使用原始模式)。 5. 顶层过滤器驱动程序典型地提供设备的增值特征,它们是可选择的且典型地由IHV提供。 下面各部分描述了驱动程序的总体类型(总线驱动程序、功能驱动程序和过滤器驱动程序)。 1.3.1.1 总线驱动程序 总线驱动程序服务一个总线控制器,适配器,或者电桥(参看图1.4),微软为大多数通用总线提供总线驱动程序,例如PCI,PnpISA,SCSI和USB。其他的总线驱动程序由IHV和OEM提供。总线驱动程序是必须的驱动程序;在一个机器里,每一类总线有一个总线驱动程序。如果机器里有不止一个同类的总线,则一个总线驱动程序能服务不止一个总线。 总线驱动程序的主要任务是: n 枚举其总线上的设备 n 响应PnP和电源管理IRP n 总线的多路访问(对某些总线) n 总体上管理其总线上的设备 在枚举过程中,一个总线驱动程序识别它的总线上的设备并为它们产生设备对象。总线驱动程序用来标识相连接的设备方法取决于特别的总线。 总线驱动程序代表其总线上的设备来执行一定的操作,包括访问设备寄存器来物理地改变设备的电源状态。例如,当设备休眠时,总线驱动程序设置设备寄存器来给设备适当的电源状态。 但要注意,总线驱动程序不能够处理其总线上的设备的读和写请求,一个设备的读和写请求由设备功能驱动程序处理(看1.4.1.2部分)。仅仅当设备以原始模式使用时,父总线驱动程序处理设备的读和写。 总线驱动程序为控制器,适配器,或者电桥起着功能驱动程序的作用,并因此为控制器,适配器,或者电桥管理设备电源策略。 总线驱动程序能够作为一个驱动程序/小驱动程序对来执行,即以SCSI端口/微端口对驱动一个SCSI HBA(主机总线适配器)的方法。在这样的驱动程序对里,小驱动程序与次一级驱动程序(它是一个DLL)相链接。 1.3.1.2 功能驱动程序 功能驱动程序是设备的主要驱动程序(参见图1.4)。一个功能驱动程序典型地由设备厂商且必须由设备厂商来编写(除非设备使用原始模式)。PnP管理器为一个设备至少装载一个功能驱动程序,功能驱动程序能服务一个或多个设备。 功能驱动程序为它的设备提供操作接口,典型地,功能驱动程序处理设备的读和写并管理设备电源策略。 一个设备的功能驱动程序能够被作为一个驱动程序/小驱动程序对来执行,例如一个端口/微端口或一个类/微类对。在这样的驱动程序对里,小驱动程序与次一级驱动程序(其是一个DLL)相链接。 如果以原始模式驱动一个设备,它没有功能驱动程序和顶层或低层过滤器驱动程序。所有的原始模型I/O通过总线驱动程序和可选择的总线过滤器驱动程序来实现。 1.3.1.3 过滤器驱动程序 过滤器驱动程序是可选择的驱动程序,该驱动程序给设备增加值或修改设备的行为。过滤器驱动程序能服务于一个或多个设备。 总线过滤器驱动程序 总线过滤器驱动程序典型地给总线添加数值,它由微软或系统OEM提供(看图1.4)。总线过滤器驱动程序是可选择的,对一个总线来说,可以有任何数目的总线过滤器驱动程序。 举例来说,总线过滤器驱动程序能够实现标准总线硬件的特性增强。 对ACPI BIOS所描述的设备,电源管理在每个这样的设备的总线驱动程序之上插入微软提供的一个ACPI过滤程序(总线过滤器驱动程序)。ACPI过滤程序执行设备电源策略,并打开和关闭设备电源,ACPI过滤程序对其他驱动程序是透明的且在非ACPI机器上是没有的。 低层过滤器驱动程序 低层过滤器驱动程序典型地修改设备硬件的行为(看图1.4),它们典型地由IHV提供且是可选择的,对一个设备来说,可以有任何数目的低层滤驱动程序。 低层设备过滤器驱动程序监视和/或修改一个特定设备的I/O请求,典型地,这些过滤程序重新定义了硬件行为来匹配期望的规范。 低层类过滤器驱动程序监视和/或修改一个设备类的I/O请求,例如,通过执行一非线性鼠标运动数据转换,鼠标设备的低层类过滤器驱动程序能够提供加速。 顶层过滤器驱动程序 顶层过滤器驱动程序典型地为一设备提供增值的特征(看图1.4),这种驱动程序通常由IHV提供且是可选择的。一个设备可有任意数目的顶层过滤器驱动程序。 顶层设备过滤器驱动程序为一个特别的设备添加数值。例如,一个键盘的顶层设备过滤器驱动程序能够加强额外的安全检查。 顶层类过滤器驱动程序为一个特别类的所有设备添加数值。 1.3.2 驱动程序层次---一个例子 这一部分描述了USB硬件可能的一套PnP驱动程序来说明PnP驱动程序的层次。 图 1.5给出了一个USB操纵杆的PnP硬件配置的例子,在图 1.5里,USB操纵杆插进一个USB集线器上的一端口。该例中的USB集线器常驻在USB主控制器面板上,且插到USB主控制器面板上的单个端口里,USB主控制器插到一个PCI总线上。从PnP的角度来看,USB集线器、USB主控制器和PCI总线都是总线设备,因为它们每一个都提供端口。操纵杆不是总线设备。 图 1.5 PnP 硬件的USB操纵杆的例子 图 1.6给出了一套驱动程序的例子,这些驱动程序可能为图 1.5 中的USB操纵杆硬件的原因而装载。 开始于图 1.6的底部,堆栈例子里的驱动程序包括: n 驱动PCI总线的一个PCI驱动程序,这是一个PnP总线驱动程序,PCI总线驱动程序由微软的系统所带。 n USB主控制器的总线驱动程序作为一个类/微类驱动程序对来执行。微软系统带有USB主控制器类和微类驱动程序。 n USB集线器总线驱动程序驱动USB集线器,微软系统提供USB集线器驱动程序。 n 操纵杆设备的三个驱动程序,其中之一是一个类/微类对。 图 1.6 PnP驱动程序层次的例子--- USB操纵杆 功能驱动程序,即USB操纵杆设备的主要驱动程序,是一个HID类驱动程序/HID USB微类驱动程序对。(HID代表“human interface device”)HID USB微类驱动程序支持HID设备的USB专用的语法,其依赖HID类驱动程序DLL对HID驱动程序的总体支持。 功能驱动程序能专用于特定的设备,或在HID状态下,一个功能驱动程序能服务一组设备。在这个例子当中,HID类/HID USB微类驱动程序对服务于一USB总线系统中的任何HID-compliant 设备。一个HID类驱动程序/HID 1394微类驱动程序对将服务于1394总线系统里的任何HID-compliant设备。 功能驱动程序由设备厂商或微软编写。在这个例子当中,功能驱动程序(HID类/HID USB微类驱动程序对)由微软编写。 在该例中,操纵杆设备有两个过滤器驱动程序:一个顶层类过滤程序添加一个宏按钮特征和一个低层设备过滤程序使操纵杆能枚举鼠标设备。 需要过滤操纵杆I/O编写者编写顶层过滤程序,操纵杆厂商则编写低层过滤器驱动程序。 n 内核模式、用户模式HID客户和应用程序不是驱动程序,但为了全面一并给出。 1.3.3 设备对象种类 一个驱动程序为它所控制的每个设备产生设备对象,设备对象代表驱动程序的设备。从PnP的角度看有三种设备对象: n 物理设备对象(PDO)---代表一个总线驱动程序的总线上的设备 n 功能设备对象(FDO)---代表一个功能驱动程序的设备 n 过滤程序设备对象(Filter DO)---代表一个过滤器驱动程序的设备 这三种设备对象都是DEVICE_OBJECT类型,但是使用方式不同并有不同的设备扩展。 通过产生一设备对象(IoCreateDevice)并将其附着到设备堆栈(IoAttachDeviceToDevice_Stack),驱动程序将其本身添加到处理设备的I/O驱动程序堆栈,IoAttachDeviceToDeviceStack决定设备堆栈当前的顶层和附着新的设备对象到设备堆栈的顶层。 图1.7给出了设备对象的可能种类,该设备对象可附着于设备堆栈里,表示处理一个设备的I/O请求的驱动程序。 这一部分描述了每一类的设备对象并注意到何时产生该类。参看第2章获得关于在必要的PnP驱动程序例程里产生设备对象的细节信息,要获得PnP设备枚举的更多信息,参见第2部分。 开始于图1.7的底部: n 总线驱动程序为总线上它所枚举的每个设备产生PDO。 当总线驱动程序枚举其设备时,它为每个子设备产生PDO。总线驱动程序枚举一设备为PnP管理器的BusRelations响应一个IRP_MN_QUERY_DEVICE_RELATIONS请求。如果自从最近一次总线驱动程序响应BusRelations的查询关系请求以来(或者这是机器被引导以来第一次查询关系)设备已经添加到总线上,则总线驱动程序为每个子设备产生一个PDO。 PDO表示了总线驱动程序的设备,其他内核模式系统组件也和它一样,如电源管理器、PnP管理器和I/O管理器。 一个设备其他的驱动程序附着于PDO顶端的设备对象,但是PDO一直在设备堆栈的底端。 n 可选择的总线过滤器驱动程序为它们过滤的每个设备产生过滤程序DO。 当PnP管理器在BusRelations列表里发现一个新设备时,它决定是否有该设备的任何总线过滤器驱动程序。如果是这样的话,对每个这样的驱动程序PnP管理器确保它们被装载(如果需要调用DriverEntry)并调用驱动程序AddDevice例程。如果总线过滤器驱动程序为这个设备过滤操作,过滤器驱动程序产生一个设备对象并附着它到AddDevice例程里的设备堆栈上。如果不止一个总线过滤器驱动程序存在,且与这个设备相关,每个这样的过滤器驱动程序产生并附着于它自己的设备对象。 n 可选择的,低层过滤器驱动程序为它们过滤的每个设备产生过滤程序DO。 如果一可选择的低层过滤器驱动程序由于这个设备的原因而存在,PnP管理器确信在总线驱动程序和任何总线过滤器驱动程序之后装载了这样的驱动程序。PnP管理器调用过滤器驱动程序的AddDevice例程,在它的AddDevice例程里,低层的过滤器驱动程序为设备产生一个过滤程序DO且附着它到设备堆栈里。如果不止一个低层过滤器驱动程序存在,每个这样的驱动程序将产生并附着它自己的过滤程序DO。 n 功能驱动程序为设备产生一个FDO。 PnP管理器确信已安装了设备的功能驱动程序并调用功能驱动程序的AddDevice例程,功能驱动程序产生一个FDO并附着它到设备堆栈里。 n 可选择的,顶层过滤器驱动程序为它们过滤的每个设备产生过滤程序DO。 如果任何可选择的,顶层过滤器驱动程序为设备而存在,PnP管理器确信在功能驱动程序调用它们的AddDevice例程之后被安装,每个这样的过滤器驱动程序附着它的设备对象到设备堆栈。 总之,设备堆栈包括每一驱动程序的设备对象,该驱动程序参与了特定设备的I/O处理。父总线驱动程序有一个PDO,功能驱动程序有一个FDO,每一个可选择的过滤器驱动程序有一个过滤程序DO。 注意到所有的设备---总线适配器/控制器设备和非总线设备---在它们的设备堆栈里有一个PDO和FDO。总线适配器/控制器的PDO由父总线的总线驱动程序产生。例如,如果一个SCSI适配器插入一个PCI总线,PCI总线驱动程序为SCSI适配器产生一个PDO。 如果一个设备正以原始模式使用,则没有功能驱动程序或过滤器驱动程序(没有FDO或过滤程序DO)。此时还有父总线驱动程序的一个PDO和零个或更多总线过滤程序DO。 要获取哪一个驱动程序例程负责产生和附着设备对象的信息,参看第2章。 设备堆栈和一些额外信息构成了一个devnode设备。在一个设备的devnode里,PnP管理器保留诸如是否设备已经启动和哪一个驱动程序,如有,登记通知设备上的改变。内核调试程序!devnode命令显示了关于一个devnode的信息。 1.3.4 设备对象---一个例子 这一部分描述了由USB硬件的驱动程序产生可能的驱动程序的设备对象来说明PnP设备对象以及它们怎样分层。 图1.8给出了在1.4.2部分里描述的驱动程序例子所产生的设备对象。 图1.8 PnP设备对象分层的例子---USB操纵杆 开始于图1.8的底部,在设备堆栈例子里的设备对象包括: 1. PCI总线的一个PDO和一个FDO 根总线驱动程序枚举内部系统总线(根总线)并为它所发现的每个设备产生一个PDO,这些PDO的其中之一支持PCI总线。(根总线的PDO和FDO没有在图中列出。) PnP管理器标识PCI驱动程序为PCI总线的功能驱动程序,安装驱动程序(如果还没安装的话),并传递PDO给PCI驱动程序。在它的AddDevice例程里,PCI驱动程序产生PCI总线(IoCreateDevice)的一个FDO并增加FDO到PCI总线的设备堆栈(IoAttachDeviceToDeviceStack)。象PCI总线的功能驱动程序一样,PCI驱动程序产生并附着这个FDO作为它的部分任务。 在该例中,没有PCI总线的过滤器驱动程序。 2. USB主控制器的一个PDO和一个FDO PnP管理器指示PCI驱动程序来启动它的设备(IRP_MN_START_DEVICE),然后查询它的子代(IRP_MN_QUERY_DEVICE_RELATIONS BusRelations)PCI驱动程序。作为响应,PCI驱动程序枚举其总线上的设备。在这个例子中,PCI驱动程序发现了一个USB主机控制器并产生那个设备的一个PDO。图中的宽箭头表示了USB主机控制器是PCI总线的一个“子代”。PCI驱动程序生成它子设备的PDO,象支持PCI总线的总线驱动程序一样,作为它的部分任务。 PnP管理器标识USB主机控制器微类/类驱动程序对作为USB主机控制器的功能驱动程序并装载该驱动程序对,PnP管理器在适当的时间调用该驱动程序对生成并附着USB主机控制器一个FDO。 在该例中,没有过滤器驱动程序支持USB主机控制器。 3. USB集线器的一个PDO和一个FDO USB主机控制器枚举它的总线、在单个端口上定位USB集线器,并为集线器生成一个PDO。USB集线器驱动程序生成并且为集线器附加一个FDO。 在该例中,没有USB集线器的过滤器驱动程序。 4. 操纵杆设备的一个PDO、一个FDO和两个过滤程序DO USB集线器驱动程序枚举它的总线、定位一个HID设备(操纵杆)和生成一个操纵杆PDO。 在该例中,低层过滤器驱动程序已经在注册表中为操纵杆设备而设置,这样,PnP管理器装载过滤器驱动程序。过滤器驱动程序决定相关设备并给设备堆栈生成和附加一个过滤程序DO。 PnP管理器决定了操纵杆设备的功能驱动程序是HID类/微类驱动程序对并装载这些驱动程序。驱动程序对包含一个链接到类驱动程序DLL的一个微类驱动程序;它们一起作为设备的一个功能驱动程序来执行。该类/微类驱动程序对生成一个设备对象、FDO,并把它附加到设备堆栈中。 顶层过滤器驱动程序生成并附加一过滤程序DO到设备堆栈,其方式与低层过滤程序类似。 注意到由父总线驱动程序生成的FDO一直处于一个特定设备的设备堆栈的底部,当驱动程序处理PnP或电源IRP时,它们必须向下通过设备堆栈传递每一个IRP给PDO和其关的总线驱动程序。 图1.9给出了与图1.8相同的设备堆栈,但强调哪一个设备对象由哪一个驱动程序生成和管理。 图1.9从驱动程序角度看到的设备对象层次的例子 一个总线驱动程序跨越多个设备堆栈,一个总线驱动程序为它的总线适配器/控制器生成FDO,并为它的每个子设备生成一个PDO。 第2章 PnP和电源管理必须的驱动程序支持 本章描述了例程和IRP处理,这些在Windows2000和WDM驱动程序里对支持即插即用(PnP)和电源管理是必须的。它覆盖了以下的主题: 2.1 必须的PnP支持的总览 2.2 PnP和电源管理DriverEntry例程 2.3 PnP和电源管理AddDevice例程 2.3.1 编写AddDevice例程的指南 2.4 Dispatch PnP例程 2.5 DispatchPower例程 2.6 PnP和电源管理Unload例程 一些驱动程序通过系统提供的端口或类驱动程序与PnP和电源系统的细节是隔离的,这样的驱动程序无需实现所有的这些机制。举个例子,一个SCSI端口驱动程序屏蔽了一个SCSI微端口驱动程序的许多电源管理和PnP系统的细节,这样一个SCSI微端口驱动程序不需要直接处理电源和PnP IRP。对这些驱动程序,参看驱动程序专用文件来获得必要的PnP支持细节。 参看第一章对PnP和电源管理的概念和术语的介绍。 2.1 必须的PnP支持的总览 一个支持PnP和电源管理的驱动程序必须具有以下功能: n 一个用来安装驱动程序的INF文件 n 一个.cat(catalog)文件,该文件有驱动程序包的WHQL数字签名 n 一个用于初始化驱动程序的DriverEntry例程 n 一个用于初始化设备的AddDevice例程 n 一个DispatchPnP例程,该例程处理PnP操作的IRP,如启动、停止和删除设备 n 一个DispatchPower例程处理电源操作的IRP n 一个Unload例程删除任何由DriverEntry设定的驱动程序专用资源 这些例程在下面的部分中讨论。参见第4部分获得关于INF文件、.cat文件和驱动程序安装的信息。 2.2 PnP和电源管理DriverEntry例程 一个DriverEntry例程初始化一个驱动程序,所有的驱动程序必须有一个DriverEntry例程。当装载驱动程序时,PnP管理器为每个驱动程序调用一次DriverEntry。在驱动程序初始化之后,PnP管理器能够调用驱动程序的AddDevice例程来初始化由该驱动程序控制的设备。 DriverEntry例程定义如下: NTSTATUS (*PDRIVER_INITIALIZE) ( IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath ); 只要驱动程序标识了链接程序的初始化例程名,你可以给初始化例程命名DriverEntry之外的某个名字。链接程序必须有初始化例程的名字来链接驱动程序的传输地址到OS装载程序里。一个名为DriverEntry的驱动程序初始化例程会自动生成该链接。 DriverEntry例程在一系统线程IRQL PASSIVE_LEVEL的环境里被调用。 一个DriverEntry例程能使用注册表来得到它所需要的初始化驱动程序的一些信息, DriverEntry例程必须在注册表里为另外的驱动程序和/或要用的被保护的子系统设置信息。为获得关于使用注册表的更多信息,请看第4部分。 一个DriverEntry例程是可分页的且应该在INIT段以便它将被丢弃。 一个DriverEntry例程必须采取以下步骤: 1. 初始化驱动程序调度表 一驱动程序必须为驱动程序AddDevice例程指定入口点、调度(Dispatch)例程、StartIo例程(如有)、Unload例程和其他任何驱动程序入口点。例如,一驱动程序应该用象下面的描述(Xxx是一个标识驱动程序的前缀)的一样,为它的AddDevice、DispatchPnp和DispatchPower例程设置入口点: : DriverObject->DriverExtension->AddDevice = XxxAddDevice; DriverObject->MajorFunction[IRP_MJ_PNP]->AddDevice = XxxDispatchPnp; DriverObject-> MajorFunction[IRP_MJ_POWER] = XxxDispatchPower; : 2. 初始化任何其他的全局驱动程序变量和数据结构。 由于一个DriverEntry例程运行在一个系统线程IRQL PASSIVE_LEVEL的环境里,只要驱动程序没有控制存有系统分页文件的设备,任何由ExAllocatePool分配且专在初始化过程中使用的内存能够从分页的池得到。这样的内存分配必须在ExFreePool在DriverEntry返回控制之前释放。 3. [可选择的] 在注册表里使用了ZwXxx和RtlXxx例程来读或者设置独立于设备的值。 4. 保存输入到DriverEntry里的RegistryPath参数。 这个参数指向一个被枚举的Unicode信息串,此Unicode信息串指定了驱动程序注册表键的一个路径:\Registry\Machine\System\CurrentControlSet\Services\Drivername。该例程应该保存一个此信息串的拷贝,而不是指针本身。在DriverEntry例程返回控制以后,指针不再有效。 5. 返回状态。 如果DriverEntry例程返回的信息不是STATUS_SUCCESS,则驱动程序不须保留装载的状态。然而,在初始化失效和返回控制之前,一个DriverEntry例程必须做下面的事情: n 释放由它设置的任何系统资源。 n 记录错误。在内核模式驱动程序设计指南节里看Error Logging and NTSTATUS Values来得到更多的信息。 2.3 PnP和电源管理AddDevice例程 在驱动程序的AddDevice例程里,驱动程序创建一个设备对象作为它的I/O设备的目标,并附着设备对象到设备堆栈,设备堆栈包括每一个相关设备驱动程序的一个设备对象。 PnP管理器调用由驱动程序控制的每个设备的一个驱动程序AddDevice例程。在设备被首次枚举时,AddDevice例程在系统初始化时被调用。当系统运行时,任何时候一个新设备被枚举,AddDevice例程被调用。 一个驱动程序的AddDevice例程应该被命名为XxxAddDevice,这里Xxx是一个标识特定的驱动程序的前缀。在DriverEntry过程中,一个驱动程序在DriverObject_>DriverExtension->AddDevice里存储了它的AddDevice例程地址:。 在一个系统线程IRQL PASSIVE_LEVEL环境里调用一个AddDevice例程。 AddDevice例程由PnP管理器定义如下: NTSTATUS (*PDRIVER_ADD_DEVICE) ( IN PDRIVER_OBJECT DriverObject, IN PDEVICE_OBJECT PhysicalDeviceObject ); DriverObject指向代表驱动程序的驱动程序对象,PhysicalDeviceObject指向被添加的 PnP设备的PDO。 在功能或过滤器驱动程序里,一个AddDevice例程应该有以下步骤: 1. 调用IoCreateDevice产生一个被添加的设备的设备对象(一个FDO)。 不要指定FDO的一个设备名,命名一个FDO绕过了PnP管理器的安全管理。如果一个用户模式组件需要一个设备的符号链接,须注册一个设备接口(看下一步)。如果一个内核模式组件需要一个早期的设备名,驱动程序必须命名FDO,但并不推荐命名。 在设备特性参数里包括FILE_DEVICE_SECURE_OPEN,这个特性指示I/O管理器来执行安全检查防止设备对象的所有打开请求,包括相对打开和尾部文件名打开。 2. [可选择的]生成一个或多个与设备的符号链接。 调用IoRegisterDeviceInterface来注册设备功能并生成一个符号链接,通过该链接应用程序或系统组件能够打开设备。当驱动程序处理IRP_MN_STRAT_DEVICE请求时,驱动程序应该激活接口(看第2部分)。 3. 在设备扩展里存储设备PDO的指针。 PnP管理器给提供一个PDO指针来作为AddDevice的PhysicalDeviceObject参数,驱动程序使用PDO指针调用诸如IoGetDeviceProperty的例程。 4. 在设备扩展里定义标志来跟踪设备一定的PnP状态,比如暂停、删除和突然的删除。 例如,定义一个标记说明当设备处于一暂停状态时,到来的IRP应该被保留。如果驱动程序还没有一个列队IRP的机制,将产生一个队列来保留IRP。为更多信息,请看第2部分。 也在设备扩展里分配一个IO_REMOVE_LOCK结构并调用IoInitializeRemoveLock来初始化该结构。 5. 如果需要,设置电源管理的DO_POWER_INRUSH或DO_POWER_PAGABLE标记,可分页的驱动程序必须设置DO_POWER_PAGABLE标记。当设备对象产生设备PDO时,设备对象标记典型地由总线驱动程序设置。然而,当高层的驱动程序产生PDO时,高层的驱动程序有时需要改变它们AddDevice例程里的这些标记值。看第1章第3部分的电源管理设置设备对象标记来获取细节。 6. 产生和/或者初始化任何其他的软件资源,驱动程序使用这些软件资源来管理这个设备,比如事件、自旋锁或者其他的对象。(硬件源,比如I/O端口,在稍后配置来响应一个IRP_MN_START_DEVICE请求。) 由于一个AddDevice例程在一个系统线程IRQL PASSIVE_LEVEL环境里运行,所以只要驱动程序没有控制保留有系统分页文件的设备,任何由ExAllocatePool分配、且专门在初始化过程中使用的内存从分页池得到。这样的内存分配必须由ExFreePoolAddDevice在返回控制之前释放。 7. 附着设备对象给设备堆栈(IoAttavhDeviceToDeviceStack)。 在TargetDevice参数里给设备的PDO指定一个指针。 存储由IoAttavhDeviceToDeviceStack返回的指针,当向下传递到设备堆栈时,这个指针是IoCallDriver和PoCallDriver的一个必要参数,该指针指向紧邻的低层的支持驱动程序的设备对象。 8. 用一个如下的状态,在FDO或者过滤程序DO里清除DO_DEVICE_INITIALATION标记:FunctionalDeviceObject->Flags &=~DO_DEVICE_INITIALIZING; 9. 准备处理设备(比如IRP_MN_QUERY_RESOURCE_REQUIREMENTS和IRP_MN_STRAT_DEVICE)的PnP IRP。 直到驱动程序接收到一个IRP_MN_STRAT_DEVICE时,它保留由PnP管理器分配给设备的硬件资源列表,才可以启动控制设备。 一个PnP总线驱动程序有一个AddDevice例程,但是,当总线驱动程序用作它的控制器和适配器的功能驱动程序时,该PnP总线驱动程序才被调用。例如,PnP管理器调用USB集线器总线驱动程序的AddDevice例程来添加集线器设备,集线器驱动程序的AddDevice例程不会为一个子集线器调用(一个插入集线器的设备)。 2.3.1 编写AddDevice例程的指南 当编写一个AddDevice例程时,考虑下面的设计指南: n 如果一个过滤器驱动程序决定它的AddDevice例程调用一个它无须服务的设备时,过滤器驱动程序必须返回STATUS_SUCCESS来允许剩余的设堆栈为设备而被装入。过滤器驱动程序没有生成一个设备对象,也不附着它到设备堆栈;过滤器驱动程序只是返回成功并允许其余的驱动程序添加到堆栈。 n 通常在一个设备对象的设备扩展里,对于它所使用的任何内核定义的对象和执行程序的自旋锁,一个驱动程序也必须提供特定对象的指针存储,这个对象从I/O管理器或其他的系统组件得到。 你也许决定分配另外的系统空间内存来满足驱动程序的需要,比如,长期缓冲区或一个辅助列表。如果是这样的话,一个AddDevice例程能够调用下面的例程: n 分页或无分页系统空间内存的ExAllocatePool n ExInitializePagedLooksideList或ExInitializeNPagedLooksideList来初始化一个分页或无分页的辅助列表 n 如果驱动程序有一个专为设备服务的线程或在任何内核定义的调度程序对象上等待时,它的AddDevice例程必须初始化调度程序对象。该调度程序对象的线程或驱动程序调用适当的KeInitializeXxx支持例程,该支持例程有一个事件指针、信号灯、互斥体和/或定时器等驱动程序提供存储的对象。 由于它在一系统线程的环境里执行,一个AddDevice例程本身能够在一个调度程序对象上等待的一个非零间隔,只要调度程序对象在等待开始之前已经被初始化。 参见内核模式驱动程序设计指南第3章的内核调度程序对象来获得更多信息。 n 如果驱动程序使用任何执行程序的自旋锁或为一个中断自旋锁提供存储,则AddDevice例程必须在传递它到任何别的支持例程之前,用每个这样的自旋锁调用KeInitializeSpinLock。看内核模式驱动程序设计指南第16章的自旋锁使用来获得更多信息。 n 当调用IoCreateDevice时,加强文件打开安全。 在调用IoCreateDevice时指定FILE_DEVICE_SECURE_OPEN特性,Windows2000和 Windows NT 4.0 SP5支持这个特性。它指令I/O管理器为所有打开的请求执行设备对象的安全检查。如果FILE_DEVICE_SECURE_OPEN特性没有在设备的类安装器INF或设备的INF上设置,并且驱动程序不执行它们自己打开请求的安全检查,在调用IoCreateDevice时厂商应该指定这个标记。 在调用IoCreateDevice时,如果一驱动程序设置了FILE_DEVICE_SECURE_OPEN标记,I/O管理器应用设备对象的安全描述符到任何相对的打开或尾部文件名打开。例如,如果FILE_DEVICE_SECURE_OPEN为\Device\foo设置,并且如果\Device\foo仅能够由管理员打开,那么,\Device\foo\abc也能够由管理员打开。然而,I/O管理器阻止一正常用户打开\Device\foo和\Device\foo\abc。 如果一个设备的驱动程序设置了FILE_DEVICE_SECURE_OPEN。PnP管理器传送它给设备的所有设备对象。 2.4 DispatchPnP例程 PnP管理器使用IRP来指导驱动程序启动、停止和删除设备并查询驱动程序的设备,所有的PnP IRP有主要的功能代码IRP_MJ_PNP。 每个PnP驱动程序对处理特定的IRP是必要的,并能够选择地处理其余的IRP。参见《Windows2000驱动程序开发参考》的卷一,来获得关于每种驱动程序(功能驱动程序、过滤器驱动程序和总线驱动程序)哪一个IRP是必要的,哪一个IRP是可选择的。 驱动程序应该在一XxxDispatchPnp例程里处理PnP IRP,这里Xxx是一个用来标识驱动程序的前缀。在驱动程序初始化它的DriverEntry例程过程中,驱动程序在DriverObject->MajorFunction[IRP_MJ_PNP]里设置它的DispatchPnp例程的地址。通过I/O管理器,PnP管理器调用一个驱动程序的DispatchPnp例程。 支持一个设备的所有驱动程序必须有机会处理设备的PnP IRP,除过在极个别情况下。(即一个功能或过滤器驱动程序允许IRP失效。)阅读第2部分获得关于PnP IRP的信息,包括何时PnP IRP被送出和它们的处理规则。 2.5 DispatchPower例程 电源管理器使用IRP指示驱动程序来改变电源状态,来等待并响应系统唤醒事件,和查询驱动程序的设备。所有的电源IRP有主功能代码IRP_MJ_POWER。 驱动程序应该在一个XxxDispatchPower例程里处理电源 IRP,这里Xxx是一个用来标识驱动程序的前缀。在DriverObject->MajorFunction[IRP_MJ_POWER]里,DriverEntry例程设置驱动程序的DispatchPower例程的地址。当一个电源IRP被送出时,PnP管理器调用驱动程序的DispatchPower例程。 DispatchPower例程应该处理仅有主功能代码IRP_MJ_POWER的IRP,该例程执行下面的任务: n 如果可能,处理IRP n 使用PoCallDriver,传递IRP给设备堆栈里下一个的低层驱动程序 n 如果是一个总线驱动程序,则执行设备上的电源操作请求和实现IRP 一个设备的所有驱动程序必须有机会处理设备的电源IRP,除过在极个别情况下。(即一个功能或过滤器驱动程序被允许使IRP失效。)大多数功能和过滤器驱动程序要么执行一些处理,要么为每个电源IRP设置一个IoCompletion例程,然后,向下传递IRP到下一个的低层驱动程序而不实现它。最后IRP到达总线驱动程序,如果需要,它物理地改变设备的电源状态并完成IRP。 在完成IRP之后,当IRP向下传递到设备堆栈时,I/O管理器调用任何由驱动程序设置的IoCompletion例程。是否一个驱动程序需要设置一个完成例程取决于IRP类型和单个的驱动程序需求。 加电一个设备的电源IRP必须首先由设备堆栈(基础总线驱动程序)里的最低层驱动程序处理,然后连续地被上面的堆栈里的每个驱动程序处理。断电一设备的电源IRP必须首先被顶部设备堆栈里的第一个驱动程序处理,然后连续地被下面的堆栈里的每个驱动程序处理。 可删除的设备的特别处理 在它们的DispatchPower例程里,可删除的设备的驱动程序应该检查是否设备仍旧存在。如果设备已被删除,驱动程序不应该向下传递IRP到下一个低层驱动程序,相反,驱动程序应该作下面的工作: n 调用PoStartNextPowerIrp来开始处理下一个电源IRP n 设置Irp->IoStatus.Status给STATUS_DELETE_PENDING n 调用IoCompleteRequest,指定IO_NO_INCREMENT,完成IRP n 返回STATUS_DELETE_PENDING 2.6 PnP和电源管理Unload例程 一个PnP驱动程序必须有一个Unload例程来删除任何驱动程序专用的资源,如内存、线程和事件等,这些由DriverEntry例程产生。如果没有驱动程序专用的资源可以删除,驱动程序必须仍就有一个Unload例程,但是它能够简单地返回。 在所有的设备已被删除之后,可在任何时候调用驱动程序的Unload例程,PnP管理器在系统线程IRQL PASSIVE_LEVEL的环境里调用一个驱动程序的Unload例程。