客户端驱动开发实战:基于 plclient 示例的 KMDF 实现解析)
示例工程【免费下载链接】Windows-driver-samplesThis repo contains driver samples prepared for use with Microsoft Visual Studio and the Windows Driver Kit (WDK). It contains both Universal Windows Driver and desktop-only driver samples.项目地址https://gitcode.com/gh_mirrors/wi/Windows-driver-samples点击查看免费下载导读本文围绕 Windows-driver-samples 仓库中powerlimit/plclient示例驱动展开讲解操作系统电源限制Power Limit管理框架下“客户端Client”一侧驱动的工作原理、WDF 实现骨架、驱动接口Device Interface / Driver Interface注册方式以及其与配套策略驱动powerlimit/plpolicy的协作机制。读完本文你将掌握 Power Limit 客户端驱动的核心数据模型POWER_LIMIT_ATTRIBUTES/POWER_LIMIT_VALUE、三个模拟 IOCTL 的语义与实现并能区分示例代码与生产驱动之间的差距知道生产环境应如何通过PoCreatePowerLimitRequest/PoQueryPowerLimitAttributes/PoSetPowerLimitValue等内核 API 与系统交互。背景Windows 电源限制框架中的 Client 与 Policy 角色Windows 操作系统提供了一套电源限制Power Limit管理基础设施操作系统或平台固件可以对系统中的功耗域Domain施加功率上限Power Limit例如限制 SoC、GPU 等组件的最大功耗从而在散热、续航与性能之间取得平衡。在这套框架中设备被划分为两类角色Power Limit Client电源限制客户端被管理的对象。它向系统描述自身支持哪些可调功耗参数例如连续功耗上限、突发功耗上限、突发时间参数并接受系统下发的目标值。对应本仓库中的 plclient 示例驱动。Power Limit Policy电源限制策略管理方。它收集输入信号、计算功耗目标并把目标值写入目标 Client。对应本仓库中的 plpolicy 示例驱动。plclient 的官方 README 明确指出plclient 是“模拟电源限制客户端设备”的驱动源码演示了操作系统如何对设备进行电源限制管理并且该示例遵循Universal Windows DriverUWD规范可使用 Visual Studio WDK 直接编译、部署。工程结构与文件职责plclient 工程位于仓库 powerlimit/plclient 目录其文件职责如下文件职责plclient.c电源限制相关的核心实现初始化/清理模拟的 limit 属性与值、实现三个驱动接口回调PLCQueryAttributes、PLCSetLimits、PLCQueryLimitValuesplclient.h设备扩展FDO_DATA、调试宏、全局锁、函数原型定义wdf.cWDF 样板代码DriverEntry、EvtDriverDeviceAdd、I/O 队列、设备接口/驱动接口注册、IOCTL 分发powerlimitclient_drvinterface.h三个模拟 IOCTL 与默认模拟参数的定义PLCLIENT_DEFAULT_*plclient.inf驱动安装 INF声明ACPI\PLCL0001与root\PLCL0001两种设备匹配plclient.aslSSDT 示例定义两个PLCL0001设备SoC0 与 GPU1plclient.vcxprojVS/WDK 工程文件DriverTypeKMDF、DriverTargetPlatformUniversalplclient.sln解决方案文件与它配套的 plpolicy 工程同样位于 powerlimit/plpolicy通过 5 个策略 IOCTL 向测试程序暴露“注册/注销/查询属性/查询值/设置值”能力其中注册、查询、设置最终都转发到内核 Po* API是理解 plclient 接口语义的最佳参照物。核心数据模型POWER_LIMIT_ATTRIBUTES 与 POWER_LIMIT_VALUEPower Limit 框架的核心是“域Domain 功耗类型Type 值/属性”的二维模型。客户端驱动在 InitPowerLimitValues 中为每个域、每种类型构造一组属性POWER_LIMIT_ATTRIBUTES和一组当前值POWER_LIMIT_VALUE。默认模拟规模由 powerlimitclient_drvinterface.h 定义// 每个域支持的功耗类型Continuous连续/ Burst突发/ BurstTimeParameter突发时间参数 #define PLCLIENT_DEFAULT_LIMIT_COUNT_PER_DOMAIN 3UL #define PLCLIENT_DEFAULT_DOMAIN_COUNT 2UL #define PLCLIENT_DEFAULT_MAX_VALUE 50000UL #define PLCLIENT_DEFAULT_MIN_VALUE 1000UL即模拟 2 个域 × 每个域 3 种类型 6 个功耗参数。初始化时每个参数的属性被填充为LimitAttributes[Index].Type Type; // PowerLimitContinuous 等 LimitAttributes[Index].DomainId DomainId; LimitAttributes[Index].MaxValue PLCLIENT_DEFAULT_MAX_VALUE; // 50000 LimitAttributes[Index].MinValue PLCLIENT_DEFAULT_MIN_VALUE; // 1000 LimitAttributes[Index].DefaultACValue POWER_LIMIT_VALUE_NO_CONTROL; LimitAttributes[Index].DefaultDCValue POWER_LIMIT_VALUE_NO_CONTROL; // 仅 Continuous 类型额外支持时间参数TimeParameter if (Type PowerLimitContinuous) { LimitAttributes[Index].MinTimeParameter PLCLIENT_DEFAULT_MIN_VALUE; LimitAttributes[Index].MaxTimeParameter PLCLIENT_DEFAULT_MAX_VALUE; LimitAttributes[Index].Flags.SupportTimeParameter 1; }对应地当前值被初始化为“无控制”状态LimitValues[Index].TargetValue POWER_LIMIT_VALUE_NO_CONTROL; LimitValues[Index].TimeParameter POWER_LIMIT_VALUE_NO_CONTROL;从代码可以推断出以下关键语义POWER_LIMIT_VALUE_NO_CONTROL表示“不施加限制”。它是TargetValue与TimeParameter的合法取值之一表示系统暂时不限制该参数见 plclient.c 的校验逻辑。取值合法性以属性为基准TargetValue必须落在[MinValue, MaxValue]区间内TimeParameter只有在该参数的Flags.SupportTimeParameter被置位时才合法且必须落在[MinTimeParameter, MaxTimeParameter]区间内。属性Attributes描述能力边界值Values表示当前状态二者按(Type, DomainId)配对索引这一对偶结构同样被 plpolicy 的接口头文件 沿用。WDF 驱动骨架从 DriverEntry 到设备接口注册plclient 是一个典型的 KMDF 驱动WDF 样板集中在 wdf.cDriverEntrywdf.c调用WdfDriverCreate创建 WDF 驱动对象注册EvtDriverDeviceAdd与EvtDriverUnload并创建全局等待锁GlobalMutex用于保护模拟状态。EvtDriverDeviceAddwdf.c创建设备对象为其挂接FDO_DATA上下文与销毁回调创建顺序分发Sequential默认队列处理 IRP_MJ_DEVICE_CONTROL调用InitPowerLimitValues初始化模拟状态。注册两种“接口”这是本示例区别于普通驱动的最重要部分// 1) 设备接口Device Interface让上层应用可以通过 GUID 打开设备发 IOCTL Status WdfDeviceCreateDeviceInterface( DeviceHandle, GUID_DEVINTERFACE_POWER_LIMIT, NULL); // 2) 驱动接口Driver Interface让其他内核驱动如策略驱动查询/设置电源限制 RtlZeroMemory(PowerLimitInterface, sizeof(PowerLimitInterface)); PowerLimitInterface.Version 1; PowerLimitInterface.Size sizeof(PowerLimitInterface); PowerLimitInterface.Context DeviceHandle; PowerLimitInterface.DomainCount PLCLIENT_DEFAULT_DOMAIN_COUNT; PowerLimitInterface.QueryAttributes PLCQueryAttributes; PowerLimitInterface.SetPowerLimit PLCSetLimits; PowerLimitInterface.QueryPowerLimit PLCQueryLimitValues; WDF_QUERY_INTERFACE_CONFIG_INIT(QueryInterfaceConfig, (PINTERFACE)PowerLimitInterface, GUID_POWER_LIMIT_INTERFACE, NULL); Status WdfDeviceAddQueryInterface(DeviceHandle, QueryInterfaceConfig);其中GUID_DEVINTERFACE_POWER_LIMIT用于向用户态暴露设备GUID_POWER_LIMIT_INTERFACE来自poclass.h则是内核驱动接口——策略驱动正是通过WdfDeviceQueryInterface取得这套回调QueryAttributes/SetPowerLimit/QueryPowerLimit来管理客户端。三个回调的实现在 plclient.cPLCQueryAttributes返回全部 6 个参数的属性数组缓冲区不足时返回STATUS_BUFFER_TOO_SMALL并通过AttributeCount报告所需数量。PLCSetLimits逐项校验(Type, DomainId)匹配、值区间合法后更新模拟值。源码注释明确说明生产驱动中这些值应作为下发到硬件寄存器的功耗目标。PLCQueryLimitValues返回当前模拟值数组。所有回调均在持锁状态下访问设备扩展并通过#pragma alloc_text(PAGE, ...)标记为可分页代码。模拟 IOCTL验证驱动的第二种通道除了内核驱动接口plclient 还提供一组仅用于测试/验证的模拟 IOCTL见 powerlimitclient_drvinterface.h由 EvtIoDeviceControl 分发IOCTL输出语义IOCTL_POWERLIMIT_CLIENT_QUERY_LIMIT_COUNT0x800ULONG返回支持的功耗参数数量本例为 6IOCTL_POWERLIMIT_CLIENT_QUERY_ATTRIBUTES0x801POWER_LIMIT_ATTRIBUTES[]返回各参数的属性IOCTL_POWERLIMIT_CLIENT_QUERY_LIMITS0x802POWER_LIMIT_VALUE[]返回各参数的当前值三个 IOCTL 均采用METHOD_BUFFEREDFILE_WRITE_DATA的缓冲式访问由宏POWERLIMITCLIENT_IOCTL生成并要求调用方提供精确大小的输出缓冲区否则返回STATUS_BUFFER_OVERFLOW。wdf.c 中源码注释明确提示“这些 IOCTL 提供了另一种验证设备驱动接口的途径生产代码并不需要它们。”也就是说模拟 IOCTL 的价值在于不依赖完整的 Po* 内核链路即可用应用层工具直接验证客户端驱动的数据模型与并发安全。这一设计思想与 plpolicy 的策略 IOCTLREGISTER / UNREGISTER / QUERY_ATTRIBUTES / QUERY_VALUES / SET_VALUES互为镜像——策略驱动通过这 5 个 IOCTL 把内核 Po* API 的能力暴露给测试程序测试程序再通过策略驱动间接操纵客户端。INF 与 ACPI 集成如何让系统枚举到模拟设备plclient 的安装与枚举信息分布在两个文件中plclient.inf 关键点[Standard.NTamd64] %PlCl.DeviceDesc% PlCl_Device, ACPI\PLCL0001 %PlCl.DeviceDesc% PlCl_Device, root\PLCL0001 [PlCl_Device.NT.Services] AddService plclient,%SPSVCINST_ASSOCSERVICE%,PlCl_Service_Inst [PlCl_Service_Inst] ServiceType 1 ; SERVICE_KERNEL_DRIVER StartType 3 ; SERVICE_DEMAND_START ErrorControl 1 ; SERVICE_ERROR_NORMAL ServiceBinary %12%\plclient.sys LoadOrderGroup Extended Base同时支持AMD64 与 ARM64两种平台INF 中分别为Standard.NTamd64/Standard.NTarm64。设备匹配 ID 为ACPI\PLCL0001ACPI 枚举路径与root\PLCL0001根枚举/手动安装路径。服务以SERVICE_DEMAND_START按需启动方式注册为内核驱动二进制位于%12%即\SystemRoot\System32\drivers。设备注册表项设置了DeviceCharacteristicsFILE_DEVICE_SECURE_OPEN与面向管理员/系统的 DACL保证访问安全。plclient.asl提供一个 SSDT 示例在\_SB.SOC0与\_SB.GPU1下各声明一个_HID PLCL0001的设备。这意味着本示例的典型使用场景是在 ACPI 表中模拟一颗 SoC 与一颗 GPU让 plclient 驱动以真实 PnP 流程接管它们从而在无需真实硬件的情况下端到端验证系统电源限制管理链路。工程文件 plclient.vcxproj 将DriverType设为KMDF、DriverTargetPlatform设为Universal与 README 中“Universal Windows Driver Compliant”的声明一致可直接用 VS WDKWindows 10/11 内核模式驱动工具集WindowsKernelModeDriver10.0构建出plclient.sys。生产驱动应如何实现从模拟到真实plclient README 与 plpolicy README 都强调模拟代码中的 IOCTL 与直接注册驱动接口仅用于示例验证生产驱动应改用操作系统内核 PoAPI 完成真正的电源限制管理*。综合两处文档与 plpolicy.c 的实现生产环境下的正确做法是客户端侧Client客户端驱动自身无需“主动”做太多事情——它只需要在设备启动时把自己支持的功耗参数数量、类型、上下限、时间参数支持情况和当前值准备好plclient 的InitPowerLimitValues即这一逻辑的模拟版通过WdfDeviceAddQueryInterfaceGUID_POWER_LIMIT_INTERFACE暴露驱动接口如示例所示供系统或策略驱动查询与下发当收到SetPowerLimit回调时把TargetValue/TimeParameter真正写入硬件示例中用内存变量模拟。策略侧Policy策略驱动的生命周期管理plpolicy README 明确列出初始化阶段订阅GUID_DEVINTERFACE_POWER_LIMIT设备接口通知在回调中查询客户端如 BIOS 名称并与目标设备比对随后通过PoCreatePowerLimitRequest创建电源限制请求用PoQueryPowerLimitAttributes查询该客户端的属性。这在 plpolicy.c 的 RegisterRequest 中可以看到完整映射——示例中的RegisterRequest正是对“PoCreatePowerLimitRequest 按名称定位设备对象GetDeviceObjectFromInterfaceName内部用ZwCreateFile/IoGetRelatedDeviceObject解析设备接口名”这一生产流程的模拟封装。运行阶段收集输入信号温度、功耗计等并计算功耗目标通过PoSetPowerLimitValue下发必要时用PoQueryPowerLimitValue查询客户端当前值。示例的SetLimitValues/QueryLimitValues分别封装了这两个 API。结束阶段当不再需要控制该客户端时通过PoDeletePowerLimitRequest删除电源限制请求。示例的UnregisterRequest与设备销毁回调EvtDeviceDestroyplpolicy wdf.c都会调用该 API 并释放登记块。其中PoCreatePowerLimitRequest/PoQueryPowerLimitAttributes/PoSetPowerLimitValue/PoQueryPowerLimitValue/PoDeletePowerLimitRequest均来自poclass.hPOWER_LIMIT_REQUEST、POWER_LIMIT_ATTRIBUTES、POWER_LIMIT_VALUE等类型同源这正是 plclient 与 plpolicy 两个示例共享的公共内核契约。总结与实践要点plclient 是一个KMDF 通用 Windows 驱动用内存变量模拟了 2 域 × 3 类型共 6 个功耗参数覆盖“属性描述 值下发 值查询”的完整客户端行为。它同时注册了用户态设备接口GUID_DEVINTERFACE_POWER_LIMIT与内核驱动接口GUID_POWER_LIMIT_INTERFACE前者支撑模拟 IOCTL 验证后者是系统/策略驱动实际调用的入口。模拟 IOCTLIOCTL_POWERLIMIT_CLIENT_QUERY_LIMIT_COUNT等仅用于测试生产客户端不需要这些 IOCTL生产策略侧则应使用PoCreatePowerLimitRequest→PoQueryPowerLimitAttributes→PoSetPowerLimitValue→PoDeletePowerLimitRequest这一套内核 API 生命周期。结合 plclient.asl 的 SSDT 与 plclient.inf 的硬件 ID可以在无真实硬件的环境下端到端演练“ACPI 枚举 → 客户端驱动加载 → 策略驱动发现并下发功耗目标”的完整流程。若需进一步深入可继续阅读本仓库中与其成对的 plpolicy README、策略接口定义 powerlimitpolicy_drvinterface.h 以及策略驱动实现 plpolicy.c三者合起来构成了 Windows 电源限制框架的完整闭环参考实现。赞分享示例工程【免费下载链接】Windows-driver-samplesThis repo contains driver samples prepared for use with Microsoft Visual Studio and the Windows Driver Kit (WDK). It contains both Universal Windows Driver and desktop-only driver samples.项目地址https://gitcode.com/gh_mirrors/wi/Windows-driver-samples点击查看免费下载相关推荐Windows 驱动开发实战基于 KMDF 编写 Non-PnP 驱动并处理四种 IOCTLWindows-driver-samples 的 ioctl/kmdf 示例深度解析Windows 驱动开发实战基于 KMDF 编写 Non PnP 驱动并处理四种 IOCTLWindows driver samples 的 ioctl/k示例工程Windows 驱动示例Toaster 系列 KMDF 驱动开发实战部署、构建与源码解析Windows 驱动示例Toaster 系列 KMDF 驱动开发实战部署、构建与源码解析 本篇文章基于 Windows driver samples 仓库示例工程Windows 驱动开发实战基于 WDK 的 V3 System DMA 示例驱动剖析SystemDma/wdmWindows 驱动开发实战基于 WDK 的 V3 System DMA 示例驱动剖析SystemDma/wdm 本篇文章围绕 Windows drive示例工程上一篇classnames 开发者指南node 测试套件、tsd 类型检查与贡献全流程下一篇深度解析OmenSuperHub惠普游戏本硬件性能调优与风扇控制实战指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考