📄 解读nt与xp的分层驱动模型.txt
字号:
Object: 812b4410 Type: (812b4048) Device
ObjectHeader: 812b43f8
HandleCount: 0 PointerCount: 5
Directory Object: e10011b0 Name: 00000001
kd> !devobj 812b4410
Device object (812b4410) is for:
00000001 \Driver\PnpManager DriverObject 812b4980
Current Irp 00000000 RefCount 0 Type 00000004 Flags 00001040
Dacl e1518a6c DevExt 812b44c8 DevObjExt 812b44d0 DevNode 812b42b8
ExtensionFlags (0000000000)
AttachedDevice (Upper) 812f6bb0 \Driver\ACPI_HAL
Device queue is not busy.
我们很容易通过上面的输出含用AttachedDevice的一行发现连接至设备00000001的是ACPI_HAL,与我们devmgmt.msc上看到的一致。实际上Attached至这一PDO的是由ACPI_HAL服务的一个称为FDO的设备。PDO与FDO本身在内部都仍是由DEVICE_OBJECT来表示的,正像前面提及的对于总线驱动开发人员通常使用DeviceExtension中的一个标志区分同个driver服务的设备是PDO还是FDO。FDO通常由driver的AddDevice入口建立,建立后使用IoAttachDeviceToDeviceStack附接至下层总线驱动提供的PDO,正像上面windbg中我们看到的一样。这个Attached操作与我们开头讨论的Legacy设备是一样的,同样是使用DEVICE_OBJECT的AttachedDevice成员。
你可能会困惹pnpmanager的AddDevice入口应该是怎样实现的。我们知道pnpmanager实现称为Root的总线驱动程序,既然是Root,肯定也就不存在Attached上谁提供的PDO。实际上你可以使用windbg看看其实现:在Free Build XP中,其只实现return STATUS_SUCCESS(xor eax,eax/ret)了。我在用checked build的时候,其只是对IRQL进行检查:RtlAssert(KeGetCurrentIrql()<=APC_LEVEL)。
当然对于ACPI_HAL或是PCI总线,其AddDevice与pnpmanager实现肯定不同,她们肯定要IoCreateDevice进立FDO,Attached至pnpmanager提供的PDO或是上层总线提供的PDO,实现层次关系。我们继续使用windbg验证我这种思路:
kd> !drvobj \Driver\ACPI_HAL
Driver object (812f6ce8) is for:
\Driver\ACPI_HAL
Driver Extension List: (id , addr)
Device Object list:
812f6a90 812f6bb0
ACPI_HAL导出的两个设备哪个是PDO,哪个是FDO呢(你应该会明白至少有一个FDO吧)。我们知道pnp管理器在发现总线后,首先会调用总线驱动程序的AddDevice入口,然后才会发各种各样的IRP_MJ_PNP的各种MinorFunction指示总线驱动程序枚举总线,对连接至上面的设备各建立PDO等等。这儿我只是描述通常情况,对于如DDK中附带的Toaster这样的虚拟总线,其枚举总线上的设备是通过应用程序发相应的IOCTL来指示PDO的建立(通过IoInvalidateDeviceRelations让pnp管理器发IRP_MJ_PNP)。当然就算是Toaster实现的这样虚拟总线,及AddDevice入口,即FDO总是先于PDO的建立。因为对于同一个DRIVER_OBJECT服务的设备,其是通过DRIVER_OBJECT的DeviceObject形成单向链表,这个DeviceObject指示链表头,由DEVICE_OBJECT的NextDevice联接成链表。而对于IoCreateDevice建立的设备,后建立的设备,总是插入表头,而FDO基本上总是最先建立的,所以总是在表尾。有了这些分析,对于ACPI_HAL上面的输出812f6bb0即是FDO,而812f6a90则是PDO。OK,这样这个PDO,则又是上层acpi.sys实现的总线驱动程序的下层PDO了。
kd> !devobj 812f6a90
Device object (812f6a90) is for:
00000052 \Driver\ACPI_HAL DriverObject 812f6ce8
Current Irp 00000000 RefCount 0 Type 0000002a Flags 00001040
Dacl e1518a6c DevExt 812f6b48 DevObjExt 812f6b60 DevNode 812f63a8
ExtensionFlags (0000000000)
AttachedDevice (Upper) 812ad960 \Driver\ACPI
Device queue is not busy.
你看看上层是不是\Driver\ACPI(AttachedDevice行)。而PCI总线又是Attached至acpi.sys实现的Micrsoft ACPI-Compliant System上的。注意acpi可不像acpi_hal一样,只有一个PDO,而PCI总线也不是第一个PDO。有了这些知识,我想你也应该可以比较容易的发现pci总线附接至哪个pdo吧。一个更简单的办法是使用!devstack命令dump PCI总线的FDO了。
kd> !drvobj pci
Driver object (812ef850) is for:
\Driver\PCI
Driver Extension List: (id , addr)
Device Object list:
812f39e8 812f3d58 812f4e40 812f4038
812f0710 812f0908 812f0c58 812f14e8
812f0e38
kd> !devstack 812f0e38
!DevObj !DrvObj !DevExt ObjectName
> 812f0e38 \Driver\PCI 812f0ef0
812dc8c0 \Driver\ACPI 812f5660 00000058
!DevNode 812f1008 :
DeviceInst is "ACPI\PNP0A03\2&daba3ff&0"
ServiceName is "pci"
devstack命令实际上使用DEVICE_OBJECT的AttachedDevice与存于DeviceObjectExtension(注意这儿是DeviceObjectExtension而不是DeviceExtension)结构成员中的AttachedTo来显示设备栈的。当然devstack命令还显示设备对应的DEVICE_NODE。DEVICE_NODE是为了支持pnp而引入的一个系统数据结构,完整的DEVICE_NODE定义是非常复杂且非常庞大的,我就不列出来了,几个重要的成员如Sibling(兄弟DEVICE_NODE),Child(子DEVICE_NODE),Parent(父DEVICE_NODE),设备状态PNP_DEVNODE_STATE,资源使用情况CM_RESOURCE_LIST,接口类型INTERFACE_TYPE,设备标识、服务名ServiceName等等。
从我的提示DeviceNode含有Sibling、Child、Parent等成员,我们很容易想到系统可能会将所有DeviceNode组成一个树(与文件系统的目录树类似),实际上正是这样的,内核变量IopRootDeviceNode指示这颗树的根。!devnode命令可以看出这个根节点的情况,如果你使用!devnode 0 1命令的话,你将活生生的看到一个windbg的devmgmt.msc版。实际上系统的SetupDi(setupapi.dll导出的用于设备安装的API)就是通过这个来dump出所有的设备的。devmgmt.msc间接的使用这些API(你可别像我一样讶异.msc文件只是一个由MMC.EXE解析的XML文件)。同样OSR的DeviceTree肯定也使用了这些API。
到现在为止你可能更加困惹,啥是啥的PDO,系统如何知道哪个总线附接至哪儿,以形成设备层次。秘密在于注册表,设备安装时通过.inf文件等向注册表加入内容指示系统的加载顺序。早先的.inf文件真的是好复杂,在我看来绝不亚于perl脚本。Windows 2000为你自动做了太多太多了(我真想知道到底做了什么,嘻嘻)。
注册表中HKLM\SYSTEM\CurrentControlSet\Enum与HKLM\SYSTEM\CurrentControlSet\Control\Class共同协作来完成这样的任务,当然与Legacy驱动程序一样离不开HKLM\SYSTEM\CurrentControlSet\Services了。为了完整的传述Windows 2000/XP分层驱动模型,有必要在最后提及一下Filter驱动程序,这是附加在总线驱动程序或是其他驱动上或下的一类驱动,用于增强,改变原有设备的某些功能。由刚提及的注册表中的Enum与Class项的UpperFilters与LowerFilters的值提供。
最后,说明一下,由Windows 2000/XP的这个分层驱动区分出很多概念,如中间层驱动程序,故名思义,如Windows 2000/XP中随处可见的类驱动程序。类驱动程序实现某一类设备的共同功能,没有牵涉到实际硬件的访问。如磁盘类驱动程序,在Windows 2000/XP中有disk.sys,tape.sys,cdrom.sys,他们均借助于classpnp.sys实现类驱动程序,以disk.sys为例,她根本不管是IDE接口或是SCSI接口,由底层的atapi.sys或是scsiport.sys这些miniport/port驱动实现与特定硬件的交互。
另外再提及一点,可以说FileSystem Filter是一个Legacy的分层驱动(只使用DEVICE_OBJECT的AttachedDevice成员。而对于网络驱动程序,如NDIS Intermediate Drivers(含NDIS Filter Intermediate Drivers与NDIS MUX Intermediate Drivers)也可以看作是分层驱动的应用,只不过在Windows 2000/XP中由Ndis Wrapper Library(ndis.sys)隐藏了太多的信息(隐藏了使用IRP的真正面目),也可以这么说ndis.sys使用其内部自身的结构定义,如NDIS_M_DRIVER_BLOCK、NDIS_MINIPORT_BLOCK、NDIS_PROTOCOL_BLOCK、NDIS_OPEN_BLOCK这些定义之间的微妙关系,自身实现了一个层次化的结构。
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -