在Windows内核中,设备节点(_DEVICE_NODE)是即插即用管理器(PnP Manager)用来表示设备树结构的关键数据结构。其中ResourceList、ResourceListTranslated和BootResources这三个字段对于理解设备资源分配机制尤为重要。作为内核开发者,我经常需要分析这些资源列表来解决设备冲突或驱动加载问题。
ResourceList(原始资源列表)存储了设备在ACPI或BIOS中声明的原始资源需求,而ResourceListTranslated(转换后资源列表)则包含了经过PnP管理器处理后实际分配给设备的系统资源。BootResources则保存了启动阶段分配给设备的临时资源。这三个资源列表的关系可以用一个简单的开发场景来解释:当我们为一个PCI设备编写驱动程序时,ResourceList就像是设备在PCI配置空间中声明的BAR(Base Address Register),而ResourceListTranslated则是操作系统最终映射到内核地址空间的物理内存区域。
在调试会话中,我们可以看到ResourceList位于_DEVICE_NODE结构体的+0x08c偏移处:
code复制+0x08c ResourceList : 0xe127b758 _CM_RESOURCE_LIST
_CM_RESOURCE_LIST是描述设备资源的核心数据结构,其定义如下:
c复制typedef struct _CM_RESOURCE_LIST {
ULONG Count;
CM_FULL_RESOURCE_DESCRIPTOR List[1];
} CM_RESOURCE_LIST;
每个CM_FULL_RESOURCE_DESCRIPTOR对应一个物理设备,包含接口类型和总线号等信息:
c复制typedef struct _CM_FULL_RESOURCE_DESCRIPTOR {
INTERFACE_TYPE InterfaceType;
ULONG BusNumber;
CM_PARTIAL_RESOURCE_LIST PartialResourceList;
} CM_FULL_RESOURCE_DESCRIPTOR;
在实际调试中,我们可以用WinDbg的dt命令详细查看这些结构:
code复制0: kd> dt _CM_FULL_RESOURCE_DESCRIPTOR 0xe127b75c
+0x000 InterfaceType : 0xf (PNPBus)
+0x004 BusNumber : 0xffffffff
+0x008 PartialResourceList : _CM_PARTIAL_RESOURCE_LIST
CM_PARTIAL_RESOURCE_DESCRIPTOR描述了具体的硬件资源类型,常见的有:
中断资源(CmResourceTypeInterrupt):
c复制struct {
UCHAR Level;
UCHAR Vector;
ULONG Affinity;
} Interrupt;
内存资源(CmResourceTypeMemory):
c复制struct {
LARGE_INTEGER Start;
ULONG Length;
} Memory;
端口资源(CmResourceTypePort):
c复制struct {
LARGE_INTEGER Start;
ULONG Length;
} Port;
在调试信息中,我们可以看到中断资源的详细参数:
code复制0: kd> dt _CM_PARTIAL_RESOURCE_DESCRIPTOR 0xe127b76c -r
+0x000 Type : 0x2 (CmResourceTypeInterrupt)
+0x001 ShareDisposition : 0x3 (CmResourceShareShared)
+0x002 Flags : 0
+0x004 u :
+0x000 Interrupt :
+0x000 Level : 9
+0x004 Vector : 9
+0x008 Affinity : 0xffffffff
ResourceList(原始资源列表)通常来自ACPI表或BIOS报告的资源需求,而ResourceListTranslated则是经过操作系统资源仲裁后的实际分配结果。两者的差异体现在:
在调试会话中,我们可以比较两者的实际内容:
code复制ResourceList at 0xe127b758:
Interrupt: Level=9, Vector=9, Affinity=0xffffffff
ResourceListTranslated at 0xe127fe58:
Interrupt: Level=0xa, Vector=0x1b1, Affinity=3
注意向量号从9变为0x1b1,这反映了操作系统重新分配的中断资源。
BootResources保存了启动过程中使用的临时资源分配,这些资源通常:
从调试输出可以看到:
code复制BootResourcesList at 0xe127cbb8:
Interrupt: Level=9, Vector=9, Affinity=0xffffffff
这与ResourceList的内容一致,表明在启动阶段直接使用了设备声明的原始资源。
在提供的调试会话中,我们看到一个ACPI设备的中断资源:
code复制Interrupt Resource:
Type: 0x2 (CmResourceTypeInterrupt)
ShareDisposition: 0x3 (CmResourceShareShared)
Level: 0xa
Vector: 0x1b1
Affinity: 3
通过IDT表查询,我们可以确认这个中断的服务例程:
code复制0: kd> !idt
...
b1: 899069a4 ACPI!ACPIInterruptServiceRoutine (KINTERRUPT 89906968)
这表明向量0x1b1确实被分配给了ACPI驱动使用。
当怀疑存在资源冲突时,可以按照以下步骤检查:
例如,如果发现某个设备无法正常工作,但其ResourceListTranslated为空,则表明资源分配可能失败了。
驱动程序通常在EvtDevicePrepareHardware回调中处理资源分配:
c复制NTSTATUS
EvtDevicePrepareHardware(
_In_ WDFDEVICE Device,
_In_ WDFCMRESLIST ResourcesRaw,
_In_ WDFCMRESLIST ResourcesTranslated
)
{
// 遍历原始资源列表
ULONG count = WdfCmResourceListGetCount(ResourcesRaw);
for (ULONG i = 0; i < count; i++) {
PCM_PARTIAL_RESOURCE_DESCRIPTOR res =
WdfCmResourceListGetDescriptor(ResourcesRaw, i);
switch (res->Type) {
case CmResourceTypePort:
// 处理端口资源
break;
case CmResourceTypeInterrupt:
// 处理中断资源
break;
}
}
// 类似处理转换后的资源列表...
return STATUS_SUCCESS;
}
在某些情况下,可能需要手动调整资源分配。这时可以:
但要注意,修改系统资源分配应当非常谨慎,通常只在内置设备的专用驱动中这样做。
使用!devnode命令查看设备状态:
code复制0: kd> !devnode 0x899c1008 6
DevNode 0x899c1008 for PDO 0x899c1de0
State = DeviceNodeStarted (0x308)
Previous State = DeviceNodeEnumerateCompletion (0x30d)
Flags (0x000000f0) DNF_ENUMERATED, DNF_IDS_QUERIED,
DNF_HAS_BOOT_CONFIG, DNF_BOOT_CONFIG_RESERVED
检查所有三个资源列表的内容差异
使用!acpi命令查看ACPI资源声明
中断亲和性设置:利用CM_PARTIAL_RESOURCE_DESCRIPTOR中的Affinity字段,可以将设备中断绑定到特定CPU核心,减少缓存同步开销。
资源共享优化:对于支持资源共享的设备(ShareDisposition=CmResourceShareShared),合理配置可以降低资源冲突概率。
延迟资源分配:对于非关键设备,可以考虑在驱动中延迟资源请求,等系统初始化完成后再申请资源。
资源缓存:驱动程序可以缓存资源列表指针,避免频繁查询,但要注意资源可能在设备重启时重新分配。
理解_Device_Node中的这三个资源列表字段,对于内核开发者和系统调试工程师来说至关重要。通过分析这些数据结构,我们能够诊断各种设备资源问题,优化系统资源配置,并编写更健壮的设备驱动程序。在实际工作中,我经常需要结合WinDbg调试输出、ACPI表信息和驱动程序日志来全面理解设备的资源分配情况。记住,ResourceList告诉我们设备想要什么,ResourceListTranslated显示系统实际给了什么,而BootResources则揭示了启动过程中的临时状态,这三者结合起来才能完整描绘设备的资源生命周期。