目录
设备类
接口类
Windows 如何安装设备
步骤 1:标识设备
步骤 2:选择设备的驱动程序包
搜索驱动程序包
选择驱动程序
步骤 3:已安装设备的驱动程序包
硬件
为设备创建硬件 ID
根枚举设备的硬件 ID
获取设备的硬件 ID 列表
硬件 ID 示例
设备栈
相关函数
IoGetAttachedDevice 栈顶获取
IoGetDeviceAttachmentBaseRef 栈底获取
IoAttachDeviceToDeviceStack 设备挂靠(接)
IoDetachDevice 移除设备挂靠(接)
设备类
为了方便设备安装,相同方式设置和配置的设备将分组到设备安装类中。
设备设置类可以定义一些常见设置,这些设置适用于该设备安装类中的所有设备,例如应插入该设备 的设备堆栈 中的筛选器驱动程序。
Microsoft 为大多数类型的设备定义设置类。 IHV 和 OEM 可以定义新的设备安装程序类,但前提是现有类都不适用。 例如,相机供应商不必定义新的设置类,因为相机属于相机设置类。 同样, (UPS) 设备的不间断电源属于电池类。
每个设备设置类都有一个与 GUID 关联的 GUID。 系统定义的安装程序类 GUID 在 Devguid.h 中定义,通常具有表单GUID_DEVCLASS_Xxx 的符号名称。
接口类
用户模式代码可以定向到其用户模式客户端的物理、逻辑或虚拟设备的任何驱动程序都必须为其用户模式客户端提供名称。 使用名称,用户模式应用程序 (或其他系统组件) 标识请求 I/O 的设备。
从 Windows 2000 开始,驱动程序不会命名设备对象。 相反,它们使用按设备接口类分组的设备接口。 设备接口类是将设备和驱动程序功能导出到其他系统组件(包括其他驱动程序)以及用户模式应用程序的一种方式。 驱动程序可以为可能向其发送用户模式 I/O 请求的每个设备对象注册和启用设备接口类的设备接口实例。 每个 设备接口类 都应表示该类中任何 设备接口 应支持或表示(例如特定 I/O 协定)的概念性功能。
每个设备接口类都与 GUID 相关联。 系统为特定于设备的头文件中的常见设备接口类定义 GUID。 供应商可以创建其他设备接口类。
例如,三种不同类型的鼠标设备可以注册属于同一设备接口类的成员 的设备接口 ,即使一个设备接口通过 USB 端口连接,另一个通过串行端口进行连接,第三种通过红外端口进行连接。 每个驱动程序将其设备注册为接口类的成员GUID_DEVINTERFACE_MOUSE。 此 GUID 在头文件 Ntddmou.h 中定义。
驱动程序可以为设备注册和启用设备 接口 ,这些设备可以控制设备和驱动程序支持的任意数量的 设备接口类 。 例如,可以装载的磁盘的驱动程序应注册其磁盘接口类 (GUID_DEVINTERFACE_DISK) 和可装载设备类 (MOUNTDEV_MOUNTED_DEVICE_GUID) 。
当驱动程序注册设备接口类的设备接口实例时,I/O 管理器会将设备和设备接口类 GUID 与符号链接名称相关联。 驱动程序必须启用设备接口,以使该符号链接可供驱动程序或应用程序用来将 I/O 发送到。 在系统启动时,链接名称的注册会持续存在,但 设备接口 必须由设备的每个枚举上的驱动程序启用。 使用特定 设备接口类 的应用程序可以查询该类中的 设备接口 实例,并接收表示支持接口的设备符号链接名称列表。 然后,应用程序可以使用符号链接名称作为 I/O 请求的目标。
Windows 如何安装设备
设备和驱动程序安装概述 - Windows drivers | Microsoft Learn
步骤 1:标识设备
当总线驱动程序向 Windows 操作系统报告新设备时,Windows需要从总线驱动程序查询有关此设备的信息以识别设备。 出于其他原因,需要此信息来标识可能 ( 此) 驱动程序包。
Windows使用这些 ID 查找设备和驱动程序包之间的最匹配
硬件 ID 或兼容 ID 的格式通常包括以下各项:
- 特定于总线的前缀,例如 PCI\ 或 USB\。
- 设备供应商特定的标识符,例如供应商、型号和修订号标识符。 ID 中这些标识符的格式也特定于总线驱动程序。
兼容 ID 通常比硬件 D 更通用,并且可能不包含特定的制造商或型号信息,可能只表示此硬件的设备类型。
Windows使用硬件 D 和兼容的 ID 搜索设备的驱动程序包。 它将设备的硬件 ID 和兼容 ID 与包 的 INF 文件中指定的这些 ID 进行比较,找到设备的匹配驱动程序包。
例如,当用户将无线局域网 (WLAN) 适配器插入连接到计算机的 USB 集线器端口时,将执行以下步骤:
-
USB 集线器驱动程序检测到设备。 中心驱动程序根据从适配器查询的信息,为设备创建硬件 ID。 例如,USB 集线器驱动程序可以创建 WLAN 适配器的硬件 ID
USB\VID_1234&PID_5678&REV_0001
。 有关 USB 硬件标识格式的信息,请参阅 USB 设备的标识符。 -
USB 集线器驱动程序通知 PnP 即插即用 (管理器) 检测到新设备。 PnP 管理器会查询中心驱动程序,以查询设备的所有硬件 ID 和兼容的 ID。 中心驱动程序可以针对同一设备创建多个硬件 ID 和兼容的 ID。
-
Windows驱动程序存储中开始搜索与设备的硬件 D 之一匹配的驱动程序包。 如果Windows找不到匹配的硬件 ID,它会搜索具有设备匹配兼容 ID 的驱动程序包。
有关此过程的信息,请参阅 Step 2: A Driver for the Device is Selected。
步骤 2:选择设备的驱动程序包
检测到新设备并进行识别后,Windows 及其设备安装组件执行以下步骤:
- Windows 搜索设备的匹配驱动程序包。 有关此步骤的详细信息,请参阅 搜索驱动程序包。
- Windows 从一个或多个驱动程序包中为设备选择最适合的驱动程序包 (s) 。 有关此步骤的详细信息,请参阅 选择驱动程序。
搜索驱动程序包
使用硬件标识符 (id) 和设备的总线驱动程序报告的兼容 id ,Windows 搜索与该设备相匹配的驱动程序包。 如果设备上的硬件 ID 或兼容 ID 与驱动程序包的inf 文件的 " INF模型" 部分条目中的 id 匹配,则驱动程序包与设备匹配。
例如,在 Windows 8 和更高版本中,如果用户将 WLAN 适配器插入 USB 集线器的端口,则会执行以下步骤:
-
USB 集线器驱动程序创建了 WLAN 适配器的硬件 id 和兼容 id 的列表后,Windows 首先在驱动程序存储区中搜索设备的匹配驱动程序包。 如果在驱动程序存储区中找到了驱动程序包,Windows 会将其安装在设备上。 这允许设备快速开始工作。
-
在单独的进程中,Windows 搜索与从驱动程序存储区中安装的驱动程序更匹配的驱动程序 Windows 更新和 DevicePath。 如果找到一个,驱动程序将暂存到驱动程序存储区中,然后安装到设备上。
有关驱动程序包搜索过程的详细信息,请参阅Windows 搜索驱动程序的位置。
注意从 Windows Vista 开始,操作系统始终从驱动程序存储区安装驱动程序包。 如果在其他位置找到了匹配的驱动程序包,则 Windows 首先将包安装到驱动程序存储区,然后再将驱动程序包安装到设备上
选择驱动程序
一旦 Windows 找到了设备的一个或多个匹配的驱动程序包,Windows 通过执行以下步骤来选择最佳的驱动程序包:
-
如果 Windows 只找到一个匹配的驱动程序包,则它会在设备上安装该驱动程序包。
-
如果 Windows 找到多个匹配的驱动程序包,Windows 首先为每个驱动程序包中的每个匹配项分配一个排名值。 如果只有一个驱动程序具有最低排名值,则它会在设备上安装该驱动程序包。
有关排名过程的详细信息,请参阅如何 Windows 对驱动程序进行排名。
-
如果多个驱动程序包具有相同的最低排名值,Windows 将使用驱动程序日期和版本为设备选择最佳驱动程序包。 日期和版本由驱动程序包的inf 文件中包含的inf DriverVer 指令指定。
步骤 3:已安装设备的驱动程序包
选择Windows设备的最佳驱动程序包后,Windows以下步骤安装驱动程序包:
-
根据驱动程序包的INF 文件中指令,Windows在设备上安装驱动程序包。 例如,它可以:
-
根据所有相关 INF CopyFiles 指令的指定,将驱动程序二进制文件和其他关联文件复制到硬盘上的位置。
-
执行由任何相关的 INF AddReg 指令指定的注册表操作。
-
从"INF 版本" 部分中的 " 类"和" ClassGuid "条目向设备分配设备 安装程序类。
-
-
在设备上安装驱动程序包后,设备将重新启动。
-
在因重启而再次处理设备过程中,即插即用 (PnP) 管理器会为设备标识相应的函数驱动程序和任何可选的筛选器驱动程序,并尝试生成设备堆栈并启动设备。
PnP 管理器为尚未加载的任何所需驱动程序调用 DriverEntry 例程。 然后,PnP 管理器调用每个驱动程序的 AddDevice 例程,从低筛选器驱动程序开始,然后调用函数驱动程序,最后调用任何上部筛选器驱动程序。 PnP 管理器将资源分配给设备(如果需要)并将IRP_MN_START_DEVICE发送到设备的驱动程序。
此步骤完成后,即可安装设备并准备好使用。
硬件
硬件 ID 是供应商定义的标识字符串,Windows用于将设备与驱动程序包匹配。
在大多数情况下,一个设备关联了多个硬件 ID。通常,硬件 ID 列表按与设备的适配程度由高到低的顺序排列。 例如,设备的概念硬件 ID 列表可能如下所示:
<Product X made by company Y with firmware revision Z>
<Product X made by company Y that is a device of type W>
为设备创建硬件 ID
设备枚举器 (总线驱动程序) 向 即插即用 Manager (PnP) 报告硬件 ID。 通常,当总线驱动程序的作者需要为设备创建新的硬件 ID 时,它会向 PnP 报告,它将使用以下通用格式之一:
<enumerator>\<enumerator-specific-device-ID>
这是单个枚举器报告给即插即用 (PnP) 管理器的单个 PnP 设备的最常见格式。
\*<generic-device-ID>
星号表示设备受多个枚举器支持,如 ISAPNP 和 BIOS。
<device-class-specific-ID>
有关详细信息,请参阅通用标识符。
已建立自己的命名约定的现有设备类可能会使用自定义格式。 有关其硬件 ID 格式的信息,请参阅此类总线的硬件规范。
硬件 ID 的字符数(不包括 NULL 终止符)必须小于 MAX_DEVICE_ID_LEN
。 此约束适用于硬件 ID 中所有字段与任何 \\
字段分隔符的长度总和。 有关详细信息,请参阅 IRP_MN_QUERY_ID 的“操作”部分。
根枚举设备的硬件 ID
根枚举设备特别,因此可以使用提供硬件 ID 的 API 创建这些设备。 具有共享泛型命名空间的硬件 ID 的根枚举设备,例如ROOT\SYSTEM
,在更新Windows时,设备管理器出现黄色砰错误图标。
为防止出现这种情况,可为具有根枚举设备的每个驱动程序使用唯一命名空间。 对于 USB 或系统设备,请使用 ROOT\[COMPANYNAME]\[DEVICENAME]
,而不是使用 ROOT\USB
或 ROOT\SYSTEM"
。 然后,在安装之前检查 devnode 是否已存在。
获取设备的硬件 ID 列表
若要查找给定设备的硬件 ID 列表,请执行以下步骤:
-
打开“设备管理器”。
-
在树中找到该设备。
-
右键单击该设备并选择“属性”。
-
选择“详细信息”选项卡。
-
在“属性”下拉列表中,选择“硬件 ID”或“兼容 ID”。
还可以通过检索设备上的 DEVPKEY_Device_HardwareIds 属性以编程方式获取硬件 ID 列表。 例如,可以使用 API(例如 IoGetDevicePropertyData、 SetupDiGetDeviceProperty 或 CM_Get_DevNode_Property)检索该属性。
此例程检索到的硬件 ID 列表是一个 REG_MULTI_SZ 值。 硬件列表中的字符的最大数目(包括每个硬件 ID 后的 NULL 终止符和最终 NULL 终止符)为 REGSTR_VAL_MAX_HCID_LEN
。 硬件 ID 列表中最多可以包含的 ID 数为 64 个。
硬件 ID 示例
下面是 PnP 设备通用标识符的示例:
root\*PNP0F08
下面是 PCI 设备标识符的示例:
PCI\VEN_1000&DEV_0001&SUBSYS_00000000&REV_02
设备栈
每个驱动程序会创建一个或多个设备对象,组织上类似链表。
结构与内存中的栈类似,DO即DEVICE_OBJECT中AttachedDevice 指向栈上更高一层的驱动DO,而DeviceExtension->AttachedTo 则指向下层的驱动。
[转载]设备栈 - Acg!Check - 博客园
相关函数
IoGetAttachedDevice 栈顶获取
//循环遍历DeviceObject->AttachedDevice,直至为NULL
PDEVICE_OBJECT IoGetAttachedDevice(IN PDEVICE_OBJECT DeviceObject
)
{//// 直到 Top 元素的 AttachedDevice == NULL //while (DeviceObject->AttachedDevice)DeviceObject = DeviceObject->AttachedDevice;return DeviceObject;
}
IoGetDeviceAttachmentBaseRef 栈底获取
//调用IopGetDeviceAttachmentBase实现
//而该函数循环遍历DeviceObject->DeviceObjectExtension->AttachedTo;直至为NULL实现
PDEVICE_OBJECT IoGetDeviceAttachmentBaseRef(IN PDEVICE_OBJECT DeviceObject
)
{KIRQL OldIrql = KeRaiseIrqlToDpcLevel();PDEVICE_OBJECT Bottom = IopGetDeviceAttachmentBase(DeviceObject);ObfReferenceObject(Bottom);KfLowerIrql(OldIrql);return Bottom;
}PDEVICE_OBJECT IopGetDeviceAttachmentBase(IN PDEVICE_OBJECT DeviceObject
)
{//// 根据 DeviceObjectExtension->AttachedTo 向下查找栈底// while (DeviceObject->DeviceObjectExtension->AttachedTo){DeviceObject = DeviceObject->DeviceObjectExtension->AttachedTo;}return DeviceObject;
}
IoAttachDeviceToDeviceStack 设备挂靠(接)
//调用IopGetDeviceAttachmentBase实现,
//而该函数循环遍历DeviceObject->DeviceObjectExtension->AttachedTo;直至为NULL实现
PDEVICE_OBJECT IoAttachDeviceToDeviceStack(IN PDEVICE_OBJECT SourceDevice,IN PDEVICE_OBJECT TargetDevice
)
{return IopAttachDeviceToDeviceStackSafe(SourceDevice, TargetDevice, NULL);// AttachedToDeviceObject 参数为 NULL
}PDEVICE_OBJECT IopAttachDeviceToDeviceStackSafe(IN PDEVICE_OBJECT SourceDevice,IN PDEVICE_OBJECT TargetDeviceOUT PDEVICE_OBJECT* AttachedToDeviceObject OPTIONAL
)
{PDEVICE_OBJECT devObj = NULL;// 返回被 attach 的 objectPDEVICE_EXTENSION SourceDeviceExtension = SourceDevice->DeviceObjectExtension;KIRQL OldIrql = KeRaiseIrqToDpcLevel();// 提升到 DISPATCH_LEVELif (IopVerifierOn == TRUE){IovAttachDeviceToDeviceStack(SourceDevice, TargetDevice);}//// 得到 stack top 的 device//devObj = IoGetAttachedDevice(TargetDevice);PDEVICE_EXTENSION DeviceExtension = devObj->DeviceObjectExtension;//// 下面进行 attach 操作//if ((devObj->Flags & DO_DEVICE_INITIALIZING == 0) &&(DeviceExtension->ExtensionFlags & (DOE_UNLOAD_PENDING | DOE_DELETE_PENDING |DOE_REMOVE_PENDING | DOE_REMOVE_PROCESSED) == 0)){devObj->Spare1++;devObj->AttachedDevice = SourceDevice;SourceDevice->StackSize = devObj->StackSize + 1;SourceDevice->AlignmentRequirement = devObj->AlignmentRequirement;SourceDevice->SectorSize = devObj->SectorSize;if (DeviceExtension->ExtensionFlags & DOE_START_PENDING){SourceDevice->DeviceObjectExtension->ExtensionFlags |= DOE_START_PENDING;}SourceDeviceExtension->AttachedTo = devObj;}else{devObj = NULL;}if (AttachedToDeviceObject != NULL){*AttachedToDeviceObject = devObj;// 返回被 attach 的 device object}KfLowerIrql(OldIrql); // 恢复原 IRQL return devObj;
}
IoAttachDeviceToDeviceStack() 函数的目的是将 SourceDevice 挂接在 TargetDevice 之上。那么 SourceDevice 将变为栈 Top 元素。
成功挂接后,函数返回原 Top 元素,如图中的 device B object。
IoDetachDevice 移除设备挂靠(接)
VOID
IoDetachDevice(IN OUT PDEVICE_OBJECT TargetDevice
)
{KIRQL OldIrql = KeRaiseIrqlToDpcLevel();if (IopVerifierOn == TRUE){IovDetachDevice(TargerDevice);}//// 删除挂接//TargetDevice->AttachedDevice->DeviceObjectExtension->AttachedTo = NULL;TargerDevice->AttachedDevice = NULL;//// 检测是否需要 Unload 或 Delete 操作//if ((TargetDevice->DeviceObjectExtension->ExtensionFlags & (DOE_UNLOAD_PENDING | DOE_DELETE_PENDING| DOE_REMOVE_PENDING)) && (TargerDevice->ReferenceCount == 0)){IopCompleteUnloadOrDelete(TargetDevice, FALSE, OldIrql);}else{KfLowerIrql(OldIrql);}
}
IoDetachDevice() 函数接受的参数是由 IoAttachDeviceToDeviceStack() 挂接成功后返回的值。
将移除 device B 元素。最后调用IopCompleteUnloadOrDelete()做 Unload 或 Delete 操作。