微型驱动程序或微型端口驱动程序作为组成驱动程序配对的一个部分。 驱动程序对(微型端口、端口)可以简化驱动程序开发。 在驱动程序对中,一个驱动程序处理整个设备集合通用的常规任务,而另一个驱动程序处理特定于单个设备的任务。 处理特定于设备的任务的驱动程序按各种名称进行,包括微型端口驱动程序、微型类驱动程序和微型驱动程序。
Microsoft提供常规驱动程序,通常独立的硬件供应商提供特定的驱动程序。 阅读本主题之前,应了解 设备节点和设备堆栈 和 I/O 请求数据包中介绍的想法。
每个内核模式驱动程序都必须实现名为 DriverEntry 的函数,该函数在加载驱动程序后不久调用。 DriverEntry 函数使用指向驱动程序实现的其他几个函数的指针填充DRIVER_OBJECT结构的某些成员。 例如,DriverEntry函数将指向驱动程序Unload函数的指针填入DRIVER_OBJECT结构的Unload成员,如下图所示。
DRIVER_OBJECT结构的 MajorFunction 成员是指向处理 I/O 请求数据包(IRPs)的函数的指针数组,如下图所示。 通常,驱动程序会用指向由驱动程序实现的、用于处理各种 IRP 的函数的指针,填充 MajorFunction 数组的几个成员。
可以根据主要函数代码对 IRP 进行分类,该代码由常量(如 IRP_MJ_READ、 IRP_MJ_WRITE或 IRP_MJ_PNP)标识。 标识主要函数代码的常量充当 MajorFunction 数组中的索引。 例如,假设驱动程序实现调度函数来处理具有主要函数代码 IRP_MJ_WRITE的 IRP。 在这种情况下,驱动程序必须使用指向调度函数的指针填充数组的 MajorFunction[IRP_MJ_WRITE] 元素。
通常,驱动程序会填充 MajorFunction 数组的某些元素,并将其余元素设置为 I/O 管理器提供的默认值。 以下示例演示如何使用 !drvobj 调试器扩展检查分析 parport 驱动程序的函数指针。
0: kd> !drvobj parport 2
Driver object (fffffa80048d9e70) is for:
\Driver\Parport
DriverEntry: fffff880065ea070 parport!GsDriverEntry
DriverStartIo: 00000000
DriverUnload: fffff880065e131c parport!PptUnload
AddDevice: fffff880065d2008 parport!P5AddDevice
Dispatch routines:
[00] IRP_MJ_CREATE fffff880065d49d0 parport!PptDispatchCreateOpen
[01] IRP_MJ_CREATE_NAMED_PIPE fffff80001b6ecd4 nt!IopInvalidDeviceRequest
[02] IRP_MJ_CLOSE fffff880065d4a78 parport!PptDispatchClose
[03] IRP_MJ_READ fffff880065d4bac parport!PptDispatchRead
[04] IRP_MJ_WRITE fffff880065d4bac parport!PptDispatchRead
[05] IRP_MJ_QUERY_INFORMATION fffff880065d4c40 parport!PptDispatchQueryInformation
[06] IRP_MJ_SET_INFORMATION fffff880065d4ce4 parport!PptDispatchSetInformation
[07] IRP_MJ_QUERY_EA fffff80001b6ecd4 nt!IopInvalidDeviceRequest
[08] IRP_MJ_SET_EA fffff80001b6ecd4 nt!IopInvalidDeviceRequest
[09] IRP_MJ_FLUSH_BUFFERS fffff80001b6ecd4 nt!IopInvalidDeviceRequest
[0a] IRP_MJ_QUERY_VOLUME_INFORMATION fffff80001b6ecd4 nt!IopInvalidDeviceRequest
[0b] IRP_MJ_SET_VOLUME_INFORMATION fffff80001b6ecd4 nt!IopInvalidDeviceRequest
[0c] IRP_MJ_DIRECTORY_CONTROL fffff80001b6ecd4 nt!IopInvalidDeviceRequest
[0d] IRP_MJ_FILE_SYSTEM_CONTROL fffff80001b6ecd4 nt!IopInvalidDeviceRequest
[0e] IRP_MJ_DEVICE_CONTROL fffff880065d4be8 parport!PptDispatchDeviceControl
[0f] IRP_MJ_INTERNAL_DEVICE_CONTROL fffff880065d4c24 parport!PptDispatchInternalDeviceControl
[10] IRP_MJ_SHUTDOWN fffff80001b6ecd4 nt!IopInvalidDeviceRequest
[11] IRP_MJ_LOCK_CONTROL fffff80001b6ecd4 nt!IopInvalidDeviceRequest
[12] IRP_MJ_CLEANUP fffff880065d4af4 parport!PptDispatchCleanup
[13] IRP_MJ_CREATE_MAILSLOT fffff80001b6ecd4 nt!IopInvalidDeviceRequest
[14] IRP_MJ_QUERY_SECURITY fffff80001b6ecd4 nt!IopInvalidDeviceRequest
[15] IRP_MJ_SET_SECURITY fffff80001b6ecd4 nt!IopInvalidDeviceRequest
[16] IRP_MJ_POWER fffff880065d491c parport!PptDispatchPower
[17] IRP_MJ_SYSTEM_CONTROL fffff880065d4d4c parport!PptDispatchSystemControl
[18] IRP_MJ_DEVICE_CHANGE fffff80001b6ecd4 nt!IopInvalidDeviceRequest
[19] IRP_MJ_QUERY_QUOTA fffff80001b6ecd4 nt!IopInvalidDeviceRequest
[1a] IRP_MJ_SET_QUOTA fffff80001b6ecd4 nt!IopInvalidDeviceRequest
[1b] IRP_MJ_PNP fffff880065d4840 parport!PptDispatchPnp
在调试器输出中,可以看到 parport.sys 实现 GsDriverEntry(驱动程序入口点)。 生成驱动程序时自动生成的 GsDriverEntry 执行一些初始化,然后调用驱动程序开发人员实现的 DriverEntry。
还可以看到并口驱动程序(在其 DriverEntry 函数中)为这些主要功能代码提供调度函数的指针。
- IRP_MJ_CREATE
- IRP_MJ_CLOSE
- IRP_MJ_READ
- IRP_MJ_WRITE
- IRP_MJ_QUERY_INFORMATION
- IRP_MJ_SET_INFORMATION
- IRP_MJ_DEVICE_CONTROL
- IRP_MJ_INTERNAL_DEVICE_CONTROL
- IRP_MJ_CLEANUP
- IRP_MJ_POWER
- IRP_MJ_SYSTEM_CONTROL
- IRP_MJ_PNP
MajorFunction 数组的剩余元素保留着指向默认调度函数 nt!IopInvalidDeviceRequest 的指针。
在调试器输出中,可以看到 parport 驱动程序为 Unload 和 AddDevice 提供了函数指针,但没有为 StartIo 提供函数指针。 AddDevice 函数异常,因为它的函数指针未存储在DRIVER_OBJECT结构中。 而是存储在AddDevice 成员中,该成员属于DRIVER_OBJECT 结构的扩展。 下图展示了并行端口驱动程序在其 DriverEntry 函数中提供的函数指针。 parport提供的函数指针是标记的。
通过使用驱动程序对提高便利性
一段时间后,随着Microsoft内外的驱动程序开发人员获得了 Windows 驱动程序模型(WDM)的经验,他们意识到了有关调度函数的一些事项:
- 调度函数基本上是样板代码。 例如,对于所有驱动程序,调度函数中IRP_MJ_PNP的大部分代码都是相同的。 它只是特定于控制特定硬件的特定驱动程序的即插即用(PnP)代码的一小部分。
- 调度函数很复杂,很难做到正确。 实现线程同步、IRP 队列和 IRP 取消等功能具有挑战性,需要深入了解作系统的工作原理。
为了简化驱动程序开发人员的需求,Microsoft创建了多个特定于技术的驱动程序模型。 首先,技术特定的模型似乎彼此大相径庭,但更仔细地发现,其中许多模型都基于这种范例:
- 驱动程序分为两个部分:一个处理常规处理,一个处理特定于特定设备的处理。
- 通用文章由Microsoft编写。
- 特定部分可由Microsoft或独立的硬件供应商编写。
假设 Proseware 和 Contoso 公司都制造了一个需要 WDM 驱动程序的玩具机器人。 另外,假设Microsoft提供了一个名为“GeneralRobot.sys”的通用机器人驱动程序。 Proseware 和 Contoso 可以编写处理其特定机器人要求的小型驱动程序。 例如,Proseware 可以编写 ProsewareRobot.sys,并且可以组合成一对驱动程序(ProsewareRobot.sys,GeneralRobot.sys),形成单个 WDM 驱动程序。 同样,驱动程序对(ContosoRobot.sys,GeneralRobot.sys)可以组合成单个 WDM 驱动程序。 其最通用的形式是,可以通过使用(specific.sys,general.sys)对来创建驱动程序。
驱动程序对中的函数指针
在(specific.sys、general.sys)对中,Windows 加载 specific.sys 并调用其 DriverEntry 函数。 specific.sys DriverEntry 函数接收指向 DRIVER_OBJECT 结构的指针。 通常,你希望 DriverEntry 使用用于调度函数的指针填充 MajorFunction 数组的多个元素。 另外,你应该预期 DriverEntry 会填充 DRIVER_OBJECT 结构体的 Unload 成员(可能还有 StartIo 成员),以及驱动程序对象扩展的 AddDevice 成员。 但是,在驱动对模型中,DriverEntry 未必会执行这个操作。 相反,specific.sys DriverEntry 函数将 DRIVER_OBJECT 结构传递给由 general.sys实现的初始化函数。 下面的代码示例演示如何在 (ProsewareRobot.sys, GeneralRobot.sys) 对中调用初始化函数。
PVOID g_ProsewareRobottCallbacks[3] = {DeviceControlCallback, PnpCallback, PowerCallback};
// DriverEntry function in ProsewareRobot.sys
NTSTATUS DriverEntry (DRIVER_OBJECT *DriverObject, PUNICODE_STRING RegistryPath)
{
// Call the initialization function implemented by GeneralRobot.sys.
return GeneralRobotInit(DriverObject, RegistryPath, g_ProsewareRobottCallbacks);
}
GeneralRobot.sys 中的初始化函数将函数指针写入 DRIVER_OBJECT 结构(及其扩展)的相应成员,以及 MajorFunction 数组的相应元素。 其想法是,当 I/O 管理器向驱动程序对发送 IRP 时,IRP 首先转到由 GeneralRobot.sys实现的调度函数。 如果 GeneralRobot.sys 可以自行处理 IRP,则不需要涉及特定驱动程序 ProsewareRobot.sys。 如果 GeneralRobot.sys 可以处理 IRP 处理的部分(但不是全部),那么它会从 ProsewareRobot.sys实现的其中一个回调函数获取帮助。 GeneralRobot.sys 在 GeneralRobotInit 调用中接收指向 ProsewareRobot 回调的指针。
在 DriverEntry 返回后的某个时点,将为 Proseware 机器人设备节点构建设备堆栈。 设备堆栈可能如下所示。
如上图所示,Proseware Robot 的设备堆栈有三个设备对象。 顶部设备对象是与筛选器驱动程序 AfterThought.sys关联的筛选器设备对象(Filter DO)。 中间设备对象是与驱动程序对(ProsewareRobot.sys,GeneralRobot.sys)关联的功能设备对象(FDO)。 驱动程序对充当设备堆栈的函数驱动程序。 底部设备对象是与 Pci.sys关联的物理设备对象(PDO)。
请注意,驱动程序对 (driver pair) 仅占据设备堆栈的一个级别,并且仅与一个设备对象相关联,即FDO。 GeneralRobot.sys 处理 IRP 时,它可能会调用 ProsewareRobot.sys 以获取帮助,但这与将请求传递到设备堆栈不同。 驱动程序对构成一个 WDM 驱动程序,该驱动程序在设备堆栈中位于同一层。 驱动程序对完成 IRP 或将其向下传递到与 Pci.sys关联的 PDO 的设备堆栈。
驱动程序对的示例
假设笔记本电脑中有一张无线网络卡,并且通过查找设备管理器来确定 netwlv64.sys 是网卡的驱动程序。 可以使用 !drvobj 调试器扩展来检查 netwlv64.sys的函数指针。
1: kd> !drvobj netwlv64 2
Driver object (fffffa8002e5f420) is for:
\Driver\netwlv64
DriverEntry: fffff8800482f064 netwlv64!GsDriverEntry
DriverStartIo: 00000000
DriverUnload: fffff8800195c5f4 ndis!ndisMUnloadEx
AddDevice: fffff88001940d30 ndis!ndisPnPAddDevice
Dispatch routines:
[00] IRP_MJ_CREATE fffff880018b5530 ndis!ndisCreateIrpHandler
[01] IRP_MJ_CREATE_NAMED_PIPE fffff88001936f00 ndis!ndisDummyIrpHandler
[02] IRP_MJ_CLOSE fffff880018b5870 ndis!ndisCloseIrpHandler
[03] IRP_MJ_READ fffff88001936f00 ndis!ndisDummyIrpHandler
[04] IRP_MJ_WRITE fffff88001936f00 ndis!ndisDummyIrpHandler
[05] IRP_MJ_QUERY_INFORMATION fffff88001936f00 ndis!ndisDummyIrpHandler
[06] IRP_MJ_SET_INFORMATION fffff88001936f00 ndis!ndisDummyIrpHandler
[07] IRP_MJ_QUERY_EA fffff88001936f00 ndis!ndisDummyIrpHandler
[08] IRP_MJ_SET_EA fffff88001936f00 ndis!ndisDummyIrpHandler
[09] IRP_MJ_FLUSH_BUFFERS fffff88001936f00 ndis!ndisDummyIrpHandler
[0a] IRP_MJ_QUERY_VOLUME_INFORMATION fffff88001936f00 ndis!ndisDummyIrpHandler
[0b] IRP_MJ_SET_VOLUME_INFORMATION fffff88001936f00 ndis!ndisDummyIrpHandler
[0c] IRP_MJ_DIRECTORY_CONTROL fffff88001936f00 ndis!ndisDummyIrpHandler
[0d] IRP_MJ_FILE_SYSTEM_CONTROL fffff88001936f00 ndis!ndisDummyIrpHandler
[0e] IRP_MJ_DEVICE_CONTROL fffff8800193696c ndis!ndisDeviceControlIrpHandler
[0f] IRP_MJ_INTERNAL_DEVICE_CONTROL fffff880018f9114 ndis!ndisDeviceInternalIrpDispatch
[10] IRP_MJ_SHUTDOWN fffff88001936f00 ndis!ndisDummyIrpHandler
[11] IRP_MJ_LOCK_CONTROL fffff88001936f00 ndis!ndisDummyIrpHandler
[12] IRP_MJ_CLEANUP fffff88001936f00 ndis!ndisDummyIrpHandler
[13] IRP_MJ_CREATE_MAILSLOT fffff88001936f00 ndis!ndisDummyIrpHandler
[14] IRP_MJ_QUERY_SECURITY fffff88001936f00 ndis!ndisDummyIrpHandler
[15] IRP_MJ_SET_SECURITY fffff88001936f00 ndis!ndisDummyIrpHandler
[16] IRP_MJ_POWER fffff880018c35e8 ndis!ndisPowerDispatch
[17] IRP_MJ_SYSTEM_CONTROL fffff880019392c8 ndis!ndisWMIDispatch
[18] IRP_MJ_DEVICE_CHANGE fffff88001936f00 ndis!ndisDummyIrpHandler
[19] IRP_MJ_QUERY_QUOTA fffff88001936f00 ndis!ndisDummyIrpHandler
[1a] IRP_MJ_SET_QUOTA fffff88001936f00 ndis!ndisDummyIrpHandler
[1b] IRP_MJ_PNP fffff8800193e518 ndis!ndisPnPDispatch
在调试器输出中,可以看到 netwlv64.sys 实现 GsDriverEntry(驱动程序入口点)。 生成驱动程序时自动生成的 GsDriverEntry 执行一些初始化,然后调用驱动程序开发人员编写的 DriverEntry。
在此示例中,netwlv64.sys 实现 DriverEntry,但 ndis.sys 实现 AddDevice、 Unload 和多个调度函数。 Netwlv64.sys 称为 NDIS 微型端口驱动程序,ndis.sys 称为 NDIS 库。 这两个模块共同构成了一对(NDIS 微型端口、NDIS 库)。
此图显示了无线网络卡的设备堆栈。 请注意,驱动程序对(netwlv64.sys,ndis.sys)只占用设备堆栈中的一个级别,并且仅与一个设备对象相关联:FDO。
可用的驱动程序对
不同的特定技术的驱动程序模型对一个驱动程序对的特定和通用部分使用各种名称。 在许多情况下,配对中的特定部分具有前缀“mini”。以下是一些可用的(具体的,普通的)配对:
- (显示微型端口驱动程序,显示端口驱动程序)
- (音频微型端口驱动程序、音频端口驱动程序)
- (存储微型端口驱动程序、存储端口驱动程序)
- (电池微型类驱动程序,电池类驱动程序)
- (HID 微型驱动程序、HID 类驱动程序)
- 更换微类驱动程序、更换端口驱动程序
- (NDIS 微型端口驱动程序,NDIS 库)
注意 如列表中所示,多个模型使用术语 类驱动 作为驱动程序对的通用部分。 此类驱动程序不同于独立类驱动程序,不同于类筛选器驱动程序。