程序一个它自己的I/0栈位置,这个栈位置是在它安装的每个IRP中的。图2.3中,驱动 程序分配的IRP没有FSD创建的栈位置。任何分配IRP给低层驱动程序的高层驱动程序通 过设置相邻的较低层驱动程序的设备对象的 Stacksize值,来决定新IRP拥有多少I/0栈 位置 图2.4更详细描述了IRP环境。 图2.4IRP中I/0栈位置的环境 如图2.4所示,IRP中每个驱动程序指定的I/0栈位置包含了下列通用的信息 主功能代码( IRP MJ XXX),指定驱动程序应该完成的基本操作 对于一些被FSD操纵的主功能代码、更高层SCSI驱动程序、和所有的PnP驱动程序, 个次功能代码( IRP MN XXX),指明驱动程序应该完成的基本操作的子项, 一组操作特定的参数,例如驱动程序用来传递数据的缓冲区的大小和起始位置 指向驱动程序创建的设备对象的指针,为请求操作描述目标设备(物理的、逻辑的或 虚拟的) 指向文件对象的指针,描述打开的文件、设备、目录或卷 文件系统驱动程序通过IRP中它的I/0栈位置访问文件对象。其他的驱动程序通常忽 略这个文件对象。 特定驱动程序操作的IRP主要和次功能代码集可以是设备类型特定的。当然,更低层驱动 程序(包括PnP函数和过滤器驱动程序)和中间层驱动程序通常操作下列基本请求集 MJ_ CREATE—一打开目标设备对象,指明它对I/0操作是现存的和可用的 IRP MJ READ一从设备传输数据 IRP MJ WRITE一传输数据到设备 IRP MJ DEVICE CONTROL—一设置(或重设)设备,通过系统定义的、设备类型特定的 I/0控制代码( IOCTL): IRP MJ CLOSE一一关闭目标设备对象 IRP MJ PNP——在设备上执行即插即用操作, IRP MJ_PNP请求由PnP管理器通过I/0 管理器发送 IRP MJ POWER—一在设备上执行电源操作, IRP MJ POWER请求被电源管理器通过I/0 管理器发送。 特定种类设备的驱动程序被请求操作的主IRP函数代码和设备I/0控制代码的详细信息 可以参见《 Windows2000驱动程序开发参考》的卷1和卷2 通常,I/O管理器至少使用两个I/0栈位置发送IRP给海量存储设备驱动程序,因为文件 系统在海量存储设备的其他驱动程序上被分层。I/0使用单个栈位置发送IRP给那些其上 没有其他分层驱动程序的驱动程序 当然,I/0管理器为增添新驱动程序到系统内任何现存驱动程序链中提供支持。例如, 个在给定磁盘分区上完成数据备份的中间层镜像驱动程序可以被插入到一对驱动程序之 间,例如图2.3中所示的文件系统驱动程序和最低层驱动程序。当这个新驱动程序将它自 己连到( attach)设备栈,I/0管理器调整所有IRP中的I/0栈空间数目,它发送这些IRP 给文件系统、镜像和最低层驱动程序。图2.3中文件系统分配的每个IRP也包含另一个针 对新镜像驱动程序的I/0栈空间 注意,在任何特定驱动程序对IRP中I/0栈空间的访问方面,这个对增添新设备到现存链 的支持包含某种约束: 分层的驱动程序链内的更高层驱动程序能安全的访问任何IRP中它自己的和相邻较低
16 程序一个它自己的 I/O 栈位置,这个栈位置是在它安装的每个 IRP 中的。图 2.3 中,驱动 程序分配的 IRP 没有 FSD 创建的栈位置。任何分配 IRP 给低层驱动程序的高层驱动程序通 过设置相邻的较低层驱动程序的设备对象的 StackSize 值,来决定新 IRP 拥有多少 I/O 栈 位置。 图 2.4 更详细描述了 IRP 环境。 图 2.4 IRP 中 I/O 栈位置的环境 如图 2.4 所示,IRP 中每个驱动程序指定的 I/O 栈位置包含了下列通用的信息: ▪ 主功能代码(IRP_MJ_XXX),指定驱动程序应该完成的基本操作 ▪ 对于一些被 FSD 操纵的主功能代码、更高层 SCSI 驱动程序、和所有的 PnP 驱动程序, 一个次功能代码(IRP_MN_XXX),指明驱动程序应该完成的基本操作的子项。 ▪ 一组操作特定的参数,例如驱动程序用来传递数据的缓冲区的大小和起始位置。 ▪ 指向驱动程序创建的设备对象的指针,为请求操作描述目标设备(物理的、逻辑的或 虚拟的) ▪ 指向文件对象的指针,描述打开的文件、设备、目录或卷 文件系统驱动程序通过 IRP 中它的 I/O 栈位置访问文件对象。其他的驱动程序通常忽 略这个文件对象。 特定驱动程序操作的 IRP 主要和次功能代码集可以是设备类型特定的。当然,更低层驱动 程序(包括 PnP 函数和过滤器驱动程序)和中间层驱动程序通常操作下列基本请求集: ▪ IRP_MJ_CREATE——打开目标设备对象,指明它对 I/O 操作是现存的和可用的; ▪ IRP_MJ_READ——从设备传输数据; ▪ IRP_MJ_WRITE——传输数据到设备; ▪ IRP_MJ_DEVICE_CONTROL——设置(或重设)设备,通过系统定义的、设备类型特定的 I/O 控制代码(IOCTL); ▪ IRP_MJ_CLOSE——关闭目标设备对象 ▪ IRP_MJ_PNP——在设备上执行即插即用操作,IRP_MJ_PNP 请求由 PnP 管理器通过 I/O 管理器发送; ▪ IRP_MJ_POWER——在设备上执行电源操作,IRP_MJ_POWER 请求被电源管理器通过 I/O 管理器发送。 特定种类设备的驱动程序被请求操作的主 IRP 函数代码和设备 I/O 控制代码的详细信息, 可以参见《Windows 2000 驱动程序开发参考》的卷 1 和卷 2。 通常,I/O 管理器至少使用两个 I/O 栈位置发送 IRP 给海量存储设备驱动程序,因为文件 系统在海量存储设备的其他驱动程序上被分层。I/O 使用单个栈位置发送 IRP 给那些其上 没有其他分层驱动程序的驱动程序。 当然,I/O 管理器为增添新驱动程序到系统内任何现存驱动程序链中提供支持。例如,一 个在给定磁盘分区上完成数据备份的中间层镜像驱动程序可以被插入到一对驱动程序之 间,例如图 2.3 中所示的文件系统驱动程序和最低层驱动程序。当这个新驱动程序将它自 己连到(attach)设备栈,I/O 管理器调整所有 IRP 中的 I/O 栈空间数目,它发送这些 IRP 给文件系统、镜像和最低层驱动程序。图 2.3 中文件系统分配的每个 IRP 也包含另一个针 对新镜像驱动程序的 I/O 栈空间。 注意,在任何特定驱动程序对 IRP 中 I/O 栈空间的访问方面,这个对增添新设备到现存链 的支持包含某种约束: ▪ 分层的驱动程序链内的更高层驱动程序能安全的访问任何 IRP 中它自己的和相邻较低
层驱动程序的I/0栈位置。这样的驱动程序必须在IRP中为相邻的较低层驱动程序创 建I/0栈位置。然而,当设计这样一个较高层驱动程序时,你不能预知什么时候新驱 动程序将被添加到现存的链中,并且恰好在你的驱动程序之下。 因此,你应该假定任何后来添加的驱动程序将处理IRP主功能代码( IRP MJ XXX), 就像替换的相邻的较低层驱动程序作的那样。 分层的驱动程序链内的最低层驱动程序能在任何IRP中安全地访问仅仅它自己的I/0 栈位置。当设计这样的驱动程序时,你不能预见什么时候(或者是否)新驱动程序将 被添加到现存链中你的驱动程序上面 在设计最低层驱动程序期间,要假定驱动程序能使用传递到它自己的I/0栈位置的信 息继续处理IRP,而不管给定IRP的开始源,以及很多驱动程序如何位于它之上。 与图2.3中所示的文件系统驱动程序类似,被添加到现存驱动程序链的任何新驱动程序能 作下列事情: 设置它自己的完成例程到IRP中。 InCompletion例程检查I/0状态块,以决定较低的 驱动程序是否成功完成IRP、取消IRP、和/或带有一个错误完成它。在完成IRP之 前,完成例程也能更新任何IRP特定的驱动程已经保存的状态,释放任何操作特定的 驱动程序已经分配的资源,等等。另外,完成例程能推迟IRP完成(通过通知I/0管 理器在IRP上更多处理被请求),并且在允许IRP完成之前,能发送另一个请求到相 邻的较低层驱动程序。 在它分配和发送请求到相邻的较低层驱动程序的IRP中,设置相邻的较低层驱动程序 的I/0栈位置。 通过在每个IRP中设置相邻的较低层驱动程序的I/0栈位置和调用 IoCallDriver来传 递任何到达的请求到较低层驱动程序。(注意,对于带有主要功能代码 IRP MJ POWER 的IRP,驱动程序必须使用 PoCalldriver。) 希望得到中间层和最低层驱动程序调用的支持例程的特定信息,以及这些驱动程序必须处 理的设备类型特定请求的信息,参见《 Windows2000驱动程序开发参考》卷2。也可参考 《 Windows2000驱动程序开发参考》的卷1得到关于PnP和电源支持例程的信息。 如图2.3所示, Windows2000文件系统是一个两部分驱动程序: 1.文件系统驱动程序(FSD),它在用户模式线程的环境中执行,这个线程调用I/0系统 服务 I/0管理器把相应的IRP发送到FSD。如果FSD为IRP创建完成例程,则这个完成例程 不必在原来用户模式线程的环境中被调用 2.一组文件系统线程,和一个可能的FSP(文件系统处理) FSD能创建一组驱动程序专用系统线程,但是为了不阻碍作I/0请求的用户模式线程 大多数FSD使用系统工作者线程。任何FSD可以创建自己的过程地址空间,它的驱动程 序专用线程在其中执行,但是系统提供的FSD避免这种做法,以节省系统存储空间。 Windows2000文件系统一般使用系统工作者线程创建并且管理IRP的内部工作队列,它们 发送IRP到一个或更多低层驱动程序,有可能对于不同的设备 如图2.3中所示的最低层驱动程序通过一组离散的驱动程序提供的例程去处理每个IRP 时,与文件系统不同,它不使用系统线程。最低层驱动程序不需要它自己的线程环境,除 非为I/0安装它自己的设备是一个非常延时的过程,以至对系统性能有显著的影响。 几乎没有最低层或者中间层的驱动程序需要创建它们自己的驱动程序专用的或者设备专用 的系统线程,并且这确实要付出性能上的代价,这通常是由向它们的线程作环境交换引起 的
17 层驱动程序的 I/O 栈位置。这样的驱动程序必须在 IRP 中为相邻的较低层驱动程序创 建 I/O 栈位置。然而,当设计这样一个较高层驱动程序时,你不能预知什么时候新驱 动程序将被添加到现存的链中,并且恰好在你的驱动程序之下。 因此,你应该假定任何后来添加的驱动程序将处理 IRP 主功能代码(IRP_MJ_XXX), 就像替换的相邻的较低层驱动程序作的那样。 ▪ 分层的驱动程序链内的最低层驱动程序能在任何 IRP 中安全地访问仅仅它自己的 I/O 栈位置。当设计这样的驱动程序时,你不能预见什么时候(或者是否)新驱动程序将 被添加到现存链中你的驱动程序上面。 在设计最低层驱动程序期间,要假定驱动程序能使用传递到它自己的 I/O 栈位置的信 息继续处理 IRP,而不管给定 IRP 的开始源,以及很多驱动程序如何位于它之上。 与图 2.3 中所示的文件系统驱动程序类似,被添加到现存驱动程序链的任何新驱动程序能 作下列事情: ▪ 设置它自己的完成例程到 IRP 中。IoCompletion 例程检查 I/O 状态块,以决定较低的 驱动程序是否成功完成 IRP、取消 IRP、和/或带有一个错误完成它。在完成 IRP 之 前,完成例程也能更新任何 IRP 特定的驱动程已经保存的状态,释放任何操作特定的 驱动程序已经分配的资源,等等。另外,完成例程能推迟 IRP 完成(通过通知 I/O 管 理器在 IRP 上更多处理被请求),并且在允许 IRP 完成之前,能发送另一个请求到相 邻的较低层驱动程序。 ▪ 在它分配和发送请求到相邻的较低层驱动程序的 IRP 中,设置相邻的较低层驱动程序 的 I/O 栈位置。 ▪ 通过在每个 IRP 中设置相邻的较低层驱动程序的 I/O 栈位置和调用 IoCallDriver 来传 递任何到达的请求到较低层驱动程序。(注意,对于带有主要功能代码 IRP_MJ_POWER 的 IRP,驱动程序必须使用 PoCallDriver。) 希望得到中间层和最低层驱动程序调用的支持例程的特定信息,以及这些驱动程序必须处 理的设备类型特定请求的信息,参见《Windows 2000 驱动程序开发参考》卷 2。也可参考 《Windows 2000 驱动程序开发参考》的卷 1 得到关于 PnP 和电源支持例程的信息。 如图 2.3 所示,Windows 2000 文件系统是一个两部分驱动程序: 1. 文件系统驱动程序(FSD),它在用户模式线程的环境中执行,这个线程调用 I/O 系统 服务 I/O 管理器把相应的 IRP 发送到 FSD。 如果 FSD 为 IRP 创建完成例程,则这个完成例程 不必在原来用户模式线程的环境中被调用。 2. 一组文件系统线程,和一个可能的 FSP(文件系统处理) FSD 能创建一组驱动程序专用系统线程,但是为了不阻碍作 I/O 请求的用户模式线程, 大多数 FSD 使用系统工作者线程。任何 FSD 可以创建自己的过程地址空间,它的驱动程 序专用线程在其中执行,但是系统提供的 FSD 避免这种做法,以节省系统存储空间。 Windows 2000 文件系统一般使用系统工作者线程创建并且管理 IRP 的内部工作队列,它们 发送 IRP 到一个或更多低层驱动程序,有可能对于不同的设备。 如图 2.3 中所示的最低层驱动程序通过一组离散的驱动程序提供的例程去处理每个 IRP 时,与文件系统不同,它不使用系统线程。最低层驱动程序不需要它自己的线程环境,除 非为 I/O 安装它自己的设备是一个非常延时的过程,以至对系统性能有显著的影响。 几乎没有最低层或者中间层的驱动程序需要创建它们自己的驱动程序专用的或者设备专用 的系统线程,并且这确实要付出性能上的代价,这通常是由向它们的线程作环境交换引起 的
大多数内核模式驱动程序,像图2.3的物理设备驱动程序一样,在一个专用线程环境中执 行:当它们被调用处理IRP时,任何线程的环境恰巧是当前的。因而,驱动程序通常维护 它们的I/0操作和服务的设备的状态信息,这些信息被存放在它们的设备对象中驱动程序 定义的部分,被称为设备扩展 每一驱动程序创建的设备对象代表一种物理、逻辑、或者虚拟设备,一个特定的驱动程序 为它处理I/0请求。有关不同种类的设备驱动程序如何使用设备对象代表它们各自的设备 的指导方针,参见本章后面的“设备对象和分层的驱动程序”。有关创建和安装设备对象 的详尽信息,参见第3章。也可参见《即插即用、电源管理和安装设计指南》,提供了 个PnP驱动程序创建的设备对象的类型的讨论。 也如图2.3所示,大多数驱动程序通过驱动程序提供的一组系统定义的标准例程,来处理 各阶段中的每一IRP,但是链中的不同层的驱动程序必然有不同的标准例程。例如,只有 最低层驱动程序处理来自物理设备的中断,因此只有最低层驱动程序拥有ISR和DPC,它 完成中断驱动的1/0操作。另一方面,因为当这样的驱动程序收到来自它的设备的中断, 它知道I/0是完成的,它不需要一个完成例程。只有较高层的驱动程序拥有一个或更多个 完成例程,像图2.3中所示的FSD一样。参见2.4节可以得到对驱动程序必须或者可以拥 有的系统定义的例程的简要介绍。第4章提供了这些驱动程序例程的一个概述,后续的章 节提供了例程特定的请求 2.3.1IRP处理的注意事项 设计内核模式驱动程序时,注意下列的事项 一个新的驱动程序必须与它所替换的任何系统提供的驱动程序一样,能处理相同的 组 IRP MJ XXX。如果其驱动程序不为那个 IRP MJ XXX定义入口点,I/0管理器将针对 个给定的I/0请求返回 STATUS INVALID DEVICE REQUEST到目标设备。设备驱动程 序也必须与任何系统提供的驱动程序一样,能为 IRP MJ DEVICE_ CONTROL请求处理同 样的Ⅰ⑩0控制代码。换句话说,通过实现比现存驱动程序为相同类型设备提供的功能 少的功能,新的设备驱动程序一定不能“中断应用” ■被插入现存驱动程序链中的新的中间层驱动程序应该象它所替换的驱动程序一样,能 识别同样的一组 IRP MJ XXX新的驱动程序能简单地传递那些请求的IRP,而不为较 低层驱动程序处理它们。然而,一个新的中间层驱动程序忽略为 IRP MJ XXX请求(新 替换的更低层的驱动程序处理这个请求)定义入口点,但不必为它上层和下层的驱动 程序“打破这个链”。 最低层驱动程序仅仅能访问被发送的任何IRP中它自己的I/0栈位置。一个较高层驱 动程序仅仅访问被发送的任何IRP中它自己的和较低层驱动程序的I/0栈位置。 每个驱动程序仅仅在IRP的I/0状态块中向较高层的驱动程序(并且最终通过I/0管 理器向用户模式的应用程序)交换信息,因为当链中的每个驱动程序完成IRP时,I/0 管理器将相应的I/0栈位置赋零。从一种 Windows nt/ Windows2000到更高的平台或 版本,试图与一个特殊的较高(或者低)的驱动程序实现后门通信的任何新驱动程序 将在可移植性和互操作性方面与其他驱动程序做出妥协 对驱动程序能为 IRP MJ_ INTERNAL DEVICEC CONTROL请求定义一组设备特定的(也 称为私有的)I/0控制编码,这个请求由比这对驱动程序高的驱动程序发送到较低的 驱动程序。然而,如果希望它们从一种 Windows n和 Windows2000到下一个平台或 版本,保持与其他驱动程序的可移植性和互操作性,这样的一对驱动程序必须符合前
18 大多数内核模式驱动程序,像图 2.3 的物理设备驱动程序一样,在一个专用线程环境中执 行:当它们被调用处理 IRP 时,任何线程的环境恰巧是当前的。因而,驱动程序通常维护 它们的 I/O 操作和服务的设备的状态信息,这些信息被存放在它们的设备对象中驱动程序 定义的部分,被称为设备扩展。 每一驱动程序创建的设备对象代表一种物理、逻辑、或者虚拟设备,一个特定的驱动程序 为它处理 I/O 请求。有关不同种类的设备驱动程序如何使用设备对象代表它们各自的设备 的指导方针,参见本章后面的“设备对象和分层的驱动程序”。有关创建和安装设备对象 的详尽信息,参见第 3 章。也可参见《即插即用、电源管理和安装设计指南》,提供了一 个 PnP 驱动程序创建的设备对象的类型的讨论。 也如图 2.3 所示,大多数驱动程序通过驱动程序提供的一组系统定义的标准例程,来处理 各阶段中的每一 IRP,但是链中的不同层的驱动程序必然有不同的标准例程。例如,只有 最低层驱动程序处理来自物理设备的中断,因此只有最低层驱动程序拥有 ISR 和 DPC,它 完成中断驱动的 I/O 操作。另一方面,因为当这样的驱动程序收到来自它的设备的中断, 它知道 I/O 是完成的,它不需要一个完成例程。只有较高层的驱动程序拥有一个或更多个 完成例程,像图 2.3 中所示的 FSD 一样。参见 2.4 节可以得到对驱动程序必须或者可以拥 有的系统定义的例程的简要介绍。第 4 章提供了这些驱动程序例程的一个概述,后续的章 节提供了例程特定的请求。 2.3.1 IRP 处理的注意事项 设计内核模式驱动程序时,注意下列的事项: ▪ 一个新的驱动程序必须与它所替换的任何系统提供的驱动程序一样,能处理相同的一 组 IRP_MJ_XXX。如果其驱动程序不为那个 IRP_MJ_XXX 定义入口点,I/O 管理器将针对 一个给定的 I/O 请求返回 STATUS_INVALID_DEVICE_REQUEST 到目标设备。设备驱动程 序也必须与任何系统提供的驱动程序一样,能为 IRP_MJ_DEVICE_CONTROL 请求处理同 样的 I/O 控制代码。 换句话说,通过实现比现存驱动程序为相同类型设备提供的功能 少的功能,新的设备驱动程序一定不能“中断应用”。 ▪ 被插入现存驱动程序链中的新的中间层驱动程序应该象它所替换的驱动程序一样,能 识别同样的一组 IRP_MJ_XXX。新的驱动程序能简单地传递那些请求的 IRP,而不为较 低层驱动程序处理它们。然而,一个新的中间层驱动程序忽略为 IRP_MJ_XXX 请求(新 替换的更低层的驱动程序处理这个请求)定义入口点,但不必为它上层和下层的驱动 程序“打破这个链”。 ▪ 最低层驱动程序仅仅能访问被发送的任何 IRP 中它自己的 I/O 栈位置。一个较高层驱 动程序仅仅访问被发送的任何 IRP 中它自己的和较低层驱动程序的 I/O 栈位置。 ▪ 每个驱动程序仅仅在 IRP 的 I/O 状态块中向较高层的驱动程序(并且最终通过 I/O 管 理器向用户模式的应用程序)交换信息,因为当链中的每个驱动程序完成 IRP 时,I/O 管理器将相应的 I/O 栈位置赋零。从一种 Windows NT/Windows 2000 到更高的平台或 版本,试图与一个特殊的较高(或者低)的驱动程序实现后门通信的任何新驱动程序 将在可移植性和互操作性方面与其他驱动程序做出妥协。 ▪ 一对驱动程序能为 IRP_MJ_INTERNAL_DEVICEC_CONTROL 请求定义一组设备特定的(也 称为私有的)I/O 控制编码,这个请求由比这对驱动程序高的驱动程序发送到较低的 驱动程序。 然而,如果希望它们从一种 Windows NT 和 Windows 2000 到下一个平台或 版本,保持与其他驱动程序的可移植性和互操作性,这样的一对驱动程序必须符合前
面的所有方针。如果你用一个私有的接口设计一对驱动程序,要注意仔细定义I/0控 制代码集。使它们尽可能通用,并且按照前述的方针设计你的一对驱动程序,从而使 得当它们从一种 Windows nt和 Windows2000平台或者版本向另一个迁移时,你(或 者其他的人)能容易地重用、替换、或者移植两个新驱动程序或其中之 24驱动程序对象和标准驱动程序例程 图2.5说明驱动程序对象,描述一个驱动程序,以及最低层和较高层驱动程序可以,或者 必须拥有的一组系统定义的(或者标准的)例程 图2.5驱动程序对象 每个名称旁边带有星号的标准例程在被调用时,被传递一个IRP。在入口处,每个被传递 IRP的标准例程也被传递一个1/O请求的目标设备对象的指针。 Ⅰ/0管理器定义驱动程序对象类型,并且使用驱动程序对象来注册和跟踪驱动程序的加载 的映像的信息。注意,驱动程序对象中 Dispatch入口点(从 DDDispatchXxx到 DDDispatchYyy)对应于被传递到IRP的I/0栈位置中的主要功能代码( IRP MJ XXX 如前面的图2.3所示,I/0管理器首先把每个IRP发送到驱动程序提供的 Dispatch例程 最低层驱动程序的 Dispatch例序通常调用I/0支持例程( loStart Packet),从而排队 (或者传递)每个IRP,这个IRP拥有驱动程序的 Startle例程的有效参数。 Startle例程 在一个特定的设备上开始请求的I/0操作。较高层的驱动程序通常没有 Startle例程,但 是它们可以有。 当驱动程序被装载时,其 DriverEntry例程连带一个指向这个驱动程序对象的指针被调 用。 Driver Entry例程在输入驱动程序对象中设置一个或多个 Dispatch入口点,这样I/0 管理器就能把IRP发送到适当的驱动程序提供的 Dispatch例程。 DriverEntry例程也在驱 动程序对象中设置驱动程序的 Startle和 Unload入口点,并且在 Driverextension中设置 AddDevice例程。 DriverEntry或者可选的 Reinitialize例程也能使用驱动程序对象(没有在图2.5中显 示)中的域在配置管理器的数据库中获得信息和/或设置信息。更多信息,可参见《即插即 用、电源管理和安装设计指南》的第4部分,第2章中的“注册表中的驱动程序信息” 2.4.1对象的不透明性 像所有系统定义对象一样,驱动程序对象是不透明的:只有定义的系统组件(这里是I/O0 管理器)“知道”对象类型的内部结构,并能直接访问对象包含的所有数据。定义的系统 组件通常输出支持例程,驱动程序和其他内核模式组件能调用它们以操纵那些组件的对 象。例如, Windows nt内核输出支持例程,在最低层驱动程序注册它的中断服务程序 (ISR)时,I/0管理器调用它们以初始化并且连接中断对象,如图2.5中所示的 DDInterruptService. 为了保持驱动程序的可移植性,考虑下面这个有关系统定义的对象的实现方针 ■驱动程序必须使用系统提供的支持例程来操纵系统定义对象。定义的系统组件能随时 改变其对象类型的内部结构
19 面的所有方针。如果你用一个私有的接口设计一对驱动程序,要注意仔细定义 I/O 控 制代码集。使它们尽可能通用,并且按照前述的方针设计你的一对驱动程序,从而使 得当它们从一种 Windows NT 和 Windows 2000 平台或者版本向另一个迁移时,你(或 者其他的人)能容易地重用、替换、或者移植两个新驱动程序或其中之一。 2.4 驱动程序对象和标准驱动程序例程 图 2.5 说明驱动程序对象,描述一个驱动程序,以及最低层和较高层驱动程序可以,或者 必须拥有的一组系统定义的(或者标准的)例程。 图 2.5 驱动程序对象 每个名称旁边带有星号的标准例程在被调用时,被传递一个 IRP。在入口处,每个被传递 IRP 的标准例程也被传递一个 I/O 请求的目标设备对象的指针。 I/O 管理器定义驱动程序对象类型,并且使用驱动程序对象来注册和跟踪驱动程序的加载 的映像的信息。注意,驱动程序对象中 Dispatch 入口点(从 DDDispatchXxx 到 DDDispatchYyy) 对应于被传递到 IRP 的 I/O 栈位置中的主要功能代码(IRP_MJ_XXX)。 如前面的图 2.3 所示,I/O 管理器首先把每个 IRP 发送到驱动程序提供的 Dispatch 例程。 最低层驱动程序的 Dispatch 例序通常调用 I/O 支持例程(IoStartPacket),从而排队 (或者传递)每个 IRP,这个 IRP 拥有驱动程序的 StartIo 例程的有效参数。StartIo 例程 在一个特定的设备上开始请求的 I/O 操作。较高层的驱动程序通常没有 StartIo 例程,但 是它们可以有。 当驱动程序被装载时,其 DriverEntry 例程连带一个指向这个驱动程序对象的指针被调 用。 DriverEntry 例程在输入驱动程序对象中设置一个或多个 Dispatch 入口点,这样 I/O 管理器就能把 IRP 发送到适当的驱动程序提供的 Dispatch 例程。DriverEntry 例程也在驱 动程序对象中设置驱动程序的 StartIo 和 Unload 入口点,并且在 DriverExtension 中设置 AddDevice 例程。 DriverEntry 或者可选的 Reinitialize 例程也能使用驱动程序对象(没有在图 2.5 中显 示)中的域在配置管理器的数据库中获得信息和/或设置信息。更多信息,可参见《即插即 用、电源管理和安装设计指南》的第 4 部分,第 2 章中的“注册表中的驱动程序信息”。 2.4.1 对象的不透明性 像所有系统定义对象一样,驱动程序对象是不透明的:只有定义的系统组件(这里是 I/O 管理器)“知道”对象类型的内部结构,并能直接访问对象包含的所有数据。定义的系统 组件通常输出支持例程,驱动程序和其他内核模式组件能调用它们以操纵那些组件的对 象。例如,Windows NT 内核输出支持例程,在最低层驱动程序注册它的中断服务程序 (ISR)时,I/O 管理器调用它们以初始化并且连接中断对象,如图 2.5 中所示的 DDInterruptService。 为了保持驱动程序的可移植性,考虑下面这个有关系统定义的对象的实现方针: ▪ 驱动程序必须使用系统提供的支持例程来操纵系统定义对象。定义的系统组件能随时 改变其对象类型的内部结构
然而,I/O管理器不输出支持例程来操纵驱动程序对象。驱动程序对象被I0管理器仅仅 用来跟踪当前加载的驱动程序。一些在驱动程序对象之内的域是不透明的:只有I/0管理 器“知道”并且只有I/0管理器使用。其他是部分地不透明的:例如,你必须知道某个域 名称,以定义 AddDevice、 Dispatch、 Startle,和 Unload入口点。然而,你既不应该试 图使用驱动程序对象内未发表的域,也不应该做有关本文中命名的任何驱动程序域的位置 的假定。否则,你不能保证从一种 Windows nt和 Windows2000个平台到另一个平台上的 移植性。 2.42标准驱动程序对象入口点 内核模式驱动程序必须在它的驱动程序对象中定义下列的入口点 至少一个 Dispatch入口点,以得到请求PnP、电源、和I/0操作的IRP 其 AddDevice例程的入口点,在 DriverOb ject-> Driverextension-> AddDevice 其 Startle例程的入口点,如果它管理它自己的IRP队列 如果驱动程序能动态地被装载和/或者替换,还需要一个 Unload入口点,从而可以释 放任何系统资源,诸如驱动程序已分配的系统对象或者内存 当系统运行时不能被替换的驱动程序,诸如键盘驱动程序,不必提供 Unload例程。 这些请求不适用于一些微端口驱动程序,相应的类别或者端口驱动程序在驱动程序对象中 定义入口点。细节参见设备类型特定的文档。 支持PnP的任何驱动程序必须拥有 Add Device例程。 AddDevice例程创建一个或多个设备 对象,它们代表物理、逻辑、或者虚拟的设备,驱动程序则为它们完成I/0请求。如图 2.5所示,I/0管理器在相应的驱动程序对象中维护驱动程序创建的设备对象的信息。 如果最低层驱动程序被设计成创建并且管理它自己的IRP队列,它不必定义 Startle入口 点。然而,大多数这样驱动程序在它们的驱动程序对象中定义 Startle入口点,并且依靠 I/0管理器为发往它们的 Startle例程的IRP排队。事实上,几乎没有最低层的系统提供 的驱动程序被设计成没有 Startle例程,即使它们为IRP创建并且保持它们自己的辅助队 要不是为了更好的性能,较高层的的驱动程序(包括FSD和PnP功能,以及过滤器驱动程 序)都可能有 Startle例程,但实际上很少这样做。相反,大多数 Windows2000文件系统 驱动程序创建并且保持内部的IRP队列。其他较高层的驱动程序要么拥有内部的IRP队 列,要么从它们的 Dispatch例程简单的传递IRP到低层的驱动程序,这些都是在每个IRP 中为更低层驱动程序安装I/0栈位置,并且可能为给定的IRP安装较高层驱动程序的 InCompletion例程之后发生的 当驱动程序的 Driver Entry例程被调用,它直接在驱动程序对象中设置 Dispatch、 Startle(如果可能)、和 Unload(如果可能)入口点,如下所示: DriverObject->MajorFunction [IRP NJ xXx]=DDDispatchXxx DriverObject->MajorFunction [IRP N] yyy ]=DDDispatchYy DriverOb ject->DriverStart lo=DDStartIo DriverObject->DriverUnload=DDUnload:
20 然而,I/O 管理器不输出支持例程来操纵驱动程序对象。驱动程序对象被 I/O 管理器仅仅 用来跟踪当前加载的驱动程序。一些在驱动程序对象之内的域是不透明的:只有 I/O 管理 器“知道”并且只有 I/O 管理器使用。其他是部分地不透明的;例如,你必须知道某个域 名称,以定义 AddDevice、Dispatch、StartIo,和 Unload 入口点。然而,你既不应该试 图使用驱动程序对象内未发表的域,也不应该做有关本文中命名的任何驱动程序域的位置 的假定。否则,你不能保证从一种 Windows NT 和 Windows 2000 个平台到另一个平台上的 移植性。 2.4.2 标准驱动程序对象入口点 内核模式驱动程序必须在它的驱动程序对象中定义下列的入口点: ▪ 至少一个 Dispatch 入口点,以得到请求 PnP、电源、和 I/O 操作的 IRP ▪ 其 AddDevice 例程的入口点,在 DriverObject->DriverExtension->AddDevice ▪ 其 StartIo 例程的入口点,如果它管理它自己的 IRP 队列 ▪ 如果驱动程序能动态地被装载和/或者替换,还需要一个 Unload 入口点,从而可以释 放任何系统资源,诸如驱动程序已分配的系统对象或者内存 当系统运行时不能被替换的驱动程序,诸如键盘驱动程序,不必提供 Unload 例程。 这些请求不适用于一些微端口驱动程序,相应的类别或者端口驱动程序在驱动程序对象中 定义入口点。细节参见设备类型特定的文档。 支持 PnP 的任何驱动程序必须拥有 AddDevice 例程。AddDevice 例程创建一个或多个设备 对象,它们代表物理、逻辑、或者虚拟的设备,驱动程序则为它们完成 I/O 请求。如图 2.5 所示,I/O 管理器在相应的驱动程序对象中维护驱动程序创建的设备对象的信息。 如果最低层驱动程序被设计成创建并且管理它自己的 IRP 队列,它不必定义 StartIo 入口 点。然而,大多数这样驱动程序在它们的驱动程序对象中定义 StartIo 入口点,并且依靠 I/O 管理器为发往它们的 StartIo 例程的 IRP 排队。事实上,几乎没有最低层的系统提供 的驱动程序被设计成没有 StartIo 例程,即使它们为 IRP 创建并且保持它们自己的辅助队 列。 要不是为了更好的性能,较高层的的驱动程序(包括 FSD 和 PnP 功能,以及过滤器驱动程 序)都可能有 StartIo 例程,但实际上很少这样做。相反,大多数 Windows 2000 文件系统 驱动程序创建并且保持内部的 IRP 队列。其他较高层的驱动程序要么拥有内部的 IRP 队 列,要么从它们的 Dispatch 例程简单的传递 IRP 到低层的驱动程序,这些都是在每个 IRP 中为更低层驱动程序安装 I/O 栈位置,并且可能为给定的 IRP 安装较高层驱动程序的 IoCompletion 例程之后发生的。 当驱动程序的 DriverEntry 例程被调用,它直接在驱动程序对象中设置 Dispatch、 StartIo(如果可能)、和 Unload(如果可能)入口点,如下所示: DriverObject->MajorFunction[IRP_NJ_xxx]=DDDispatchXxx; : : DriverObject->MajorFunction[IRP_NJ_yyy]=DDDispatchYyy; : : DriverObject->DriverStartIo=DDStartIo; DriverObject->DriverUnload=DDUnload; : :