没有时间编写总线驱动程序?尝试使用设备对象命名空间

假设您在为包含几个不同类型传感器的设备编写驱动程序。您想要允许多个客户打开各个传感器并同时访问某个给定的传感器,但是要避免编写总线驱动程序的复杂性(通过即插即用公开每个传感器)。

您首先想到的可能是为每个传感器创建一个设备对象,但是有更简单的解决方法:使用文件系统风格的表示法为设备对象定义一个“传感器命名空间”。驱动程序可以通过分析创建请求中的文件名来确定当前打开的是哪个传感器。您甚至可以通过单独的即插即用设备接口来公开不同的传感器(通过为每个传感器创建一个设备对象无法实现)。可以在驱动程序中为 Microsoft Windows NT 4.0 和更高版本的 Windows 使用命名空间技术。

在 WDM 驱动程序中,每个设备对象都有一个关联的命名空间。设备命名空间中的名称是以设备名称开头的路径。如果驱动程序支持打开其设备命名空间的请求,那么它将打开请求视为请求打开文件。但是,“文件”可以是适用于驱动程序的任何项目。对于传感器示例而言,这些项目是设备中的传感器。

以下讨论如何为传感器示例实现一个命名空间。假设您的设备对象是 \device\sensors。这是您的命名空间的根目录。客户始终可以打开 \device\sensors,不管是直接打开还是通过指向 \device\sensors 的符号链接 (\\.\Sensors) 间接打开。客户还可以使用与文件系统中的文件相同的表示法来打开各个传感器:\device\sensors\1\device\sensors\2 或甚至 \device\sensors\temperature\outside\device\sensors\ 之后的“尾部名称”指定客户想要打开哪个传感器。符号链接也是这样工作。例如,如果您设置一个到 \device\sensors 的符号链接,那么客户可以打开 \\.\sensors\1\\.\sensors\2\\.\sensors\temperature\outside 等。

所有创建请求 (IRP_MJ_CREATE) 都被发送到驱动程序的 DispatchCreate 例程。驱动程序可以确定客户试图打开哪个传感器,方法是检查创建请求的 I/O 堆栈位置中的 FileObject >FileName。(请记住,FileName 是从分页的内存池分配的,因此必须以 PASSIVE_LEVEL 进行访问。)FileName 是一个 Unicode 字符串,是设备上打开的传感器的名称(例如 temperature\outside)或 NULL(这表示调用方在打开设备对象本身,也就是 \device\sensors\)。如果 FileName 包含一个字符串,那么该字符串是“尾部名称”,驱动程序可以对其进行分析,识别当前打开的是哪个传感器。因此,如果客户打开 \device\sensors\temperature\outside,那么 FileName 就是 "\temperature\outside"。

现在应该做什么?分析 FileName 中的字符串,并将想要保留的所有信息存储在将来读/写请求可以访问的地方,从而您可以识别传递此文件对象的 I/O 请求的目标传感器。您至少必须存储或分析 FileName 字符串,因为这个字符串仅在 DispatchCreate 调用期间有效,不能在将来的读/写请求中进行访问以查看在使用哪个传感器。

设备扩展不能达到这个目的,因为设备对象的所有打开的句柄共享设备扩展。您应该分配存储空间并从文件对象的 FsContext 指向它,文件对象与该特定的句柄相关联(从而与该客户和传感器关联)。驱动程序接收到的每个 I/O 请求在其 I/O 堆栈位置中都有相应的文件对象,因此您可以检查 FsContext 处每个客户的上下文来了解客户想要访问哪个传感器。如果驱动程序所在的堆栈已经将 FsContextFsContext2 用于其它目的,那么创建一个查找表将 PFILE_OBJECT 映射到您的上下文指针并将表存储在设备扩展中。

如果驱动程序允许多个客户同时访问给定的传感器,那么您还需要为每个传感器保留一个引用计数。在驱动程序的 DispatchCreate 中增加引用计数,在 DispatchClose 中减少引用计数。

当客户使用完传感器时,它调用 CloseHandle,CloseHandle 使用文件对象(对应于发送到驱动程序的句柄)产生一个或多个清除请求 (IRP_MJ_CLEANUP)。驱动程序应该使用 STATUS_CANCELLED(可能需要取消对底层驱动程序的请求)完成此文件对象的所有未决的 I/O。完成所有未决的 I/O 之后,驱动程序接收到一个关闭请求 (IRP_MJ_CLOSE)。作为响应,驱动程序应该释放 FsContext 结构、减少单个传感器数据的引用计数,并且如果没有其它客户使用传感器,就执行关闭传感器涉及到的任何其它任务(例如减小功率)。

如果您在编写 PnP 驱动程序,那么您可以对设备接口使用这项技术。IoRegisterDeviceInterface 接收一个可选的 ReferenceString 参数,通常为 NULL。如果您为这个参数提供一个字符串,那么 I/O 管理器将其追加到为接口创建的符号链接上。然后,当客户枚举接口并用它打开相应的设备路径时,引用字符串(以及客户可能提供的任何附加的字符串)被追加到创建请求的文件对象名称。例如,如果 ReferenceString 是 "temperature",那么 IoRegisterDeviceInterface 返回的符号链接将是 \\.\<符号链接>\temperature

使用设备接口的引用字符串会创建一个唯一的名称,您可以用来区别各个项目。例如,如果您有两个不同的传感器,那么您可能为每个传感器实现一个设备接口。客户可以使用设备安装函数 (SetupDiXxx) 发现和打开所有传感器,但是驱动程序可以使用引用字符串区分各个传感器。

请记住,人们设计命名空间用于文件系统,它管理安全性、独占访问等功能,因此驱动程序必须使用其设备命名空间管理这些功能。始终应该在设备对象的特性中指定 FILE_DEVICE_SECURE_OPEN,从而使得 I/O 管理器将设备对象的安全描述符应用于所有打开的请求(包括文件打开请求)。

您还应该确保打开设备对象不是用于独占访问。设备对象标志中的 DO_EXCLUSIVE 标志提供设备级别的独占访问。这个标志默认情况下被清除,从而提供非独占访问。与一些其它的设备对象标志不同,驱动程序不直接设置或清除 DO_EXCLUSIVE。对于 WDM 驱动程序,通过类或设备 INF 来启用独占访问,对于非 WDM 驱动程序或以原始模式操作的设备,通过将 Exclusive 设置为 TRUE(在调用 IoCreateDeviceSecure 时)来启用独占访问。如果设置了 DO_EXCLUSIVE,那么同一时间只有一个客户可以打开设备命名空间内的任何 项目。如果您想要为给定的项目提供独占访问,那么如前所述保留单个项目的引用计数,如果引用计数非零,则不要创建请求。

Windows Server 2003 SP1 DDK 中的 USB 大数据量传输客户驱动程序示例 (bulk usb.sys) 实现一个命名空间,允许客户通过名称打开单个大数据量传输管道。详细信息请参见 %winddk%\src\wdm\bulkusb\ 上的示例源代码。

您应该做什么?
如果您在编写一个访问多个项目的驱动程序,那么实现一个命名空间比使用多个设备对象或总线驱动程序更好:

使用文件系统风格的表示法来标识您的设备支持的每个项目。

检查创建请求的 I/O 堆栈位置中的 FileObject >FileName,确定正在打开哪个项目。

为单个客户信息分配存储空间(包括文件名字符串)并从文件对象的 FsContext 指向它,或者创建一个查找表将 PFILE_OBJECT 映射到上下文指针并将表存储到设备扩展中。

如果您在编写 PnP 驱动程序,则使用设备接口区分各种项目并使用引用字符串标识各个项目。

确保打开设备对象不是用于独占访问。

维护每个项目被打开的次数的引用计数。如果您想要打开一个项目用于一个客户独占使用,那么在使用项目期间拒绝任何创建请求。

在设备对象的特性中指定 FILE_DEVICE_SECURE_OPEN,从而使得 I/O 管理器将设备对象的安全描述符应用于项目和设备本身的打开请求。

更多信息:
您的设备命名空间有多安全?
处理 IRP:每个驱动程序作者都需要知道的技巧

Windows DDK
设备对象简介
为设备对象指定独占访问
控制设备命名空间访问
FILE_OBJECT

使用大数据量传输客户驱动程序示例 (Bulkusb.sys)
%winddk%\src\wdm\usb\bulkusb\



此信息有用吗?