21、WMI与事件追踪:WMI提供者、MOF文件、事件追踪(ETW)、性能计数器、日志记录
说到Windows内核开发中的监控与诊断,WMI和ETW是绕不开的两座大山。我早年做驱动开发时,总觉得这些是"上层的事",直到被运维同事追着问"你的驱动为啥蓝屏了连个日志都没有"——嗯,从那以后我就老老实实把WMI和ETW补上了。
说白了,WMI(Windows Management Instrumentation)就是Windows的管理接口,让用户态程序能查询和设置系统信息。而ETW(Event Tracing for Windows)则是事件追踪框架,性能开销极低,是内核级日志记录的首选。
21.1 WMI提供者:让驱动"开口说话"
WMI提供者说白了就是驱动暴露给用户态的管理接口。用户可以用PowerShell或者C#代码查询你的驱动状态,甚至修改配置。我习惯把WMI提供者分成两类:一类是只读的,用来汇报状态;另一类是可写的,用来下发命令。
实现WMI提供者需要注册一个COM组件,实现IWbemProviderInit和IWbemServices接口。内核驱动里通常用KMDF的WMI支持来简化这个过程。
// WMI提供者初始化示例
NTSTATUS WmiProviderInit(
_In_ PDEVICE_OBJECT DeviceObject,
_In_ PIRP Irp,
_In_ PWMIREGINFO WmiRegInfo
)
{
// 注册WMI回调
WmiRegInfo->GuidCount = 1;
WmiRegInfo->WmiRegGuid = &MyWmiGuid;
WmiRegInfo->Flags = WMIREG_FLAG_INSTANCE_PDO;
// 我习惯在这里注册数据块和事件块
// 数据块用于查询,事件块用于主动通知
return WmiSystemControl(DeviceObject, Irp, WmiRegInfo);
}
21.2 MOF文件:描述你的数据
MOF(Managed Object Format)文件是WMI的"身份证"。它用文本格式描述了你的驱动暴露了哪些类、属性、方法。用户态工具通过解析MOF文件,才知道怎么跟你的驱动交互。
你想想看,如果没有MOF文件,用户态程序怎么知道你的驱动提供了哪些数据?只能靠猜。所以MOF文件是WMI提供者的标配。
// MOF文件示例
#pragma namespace("\\\\.\\root\\wmi")
[WMI,
Dynamic,
Provider("MyDriverProvider"),
Description("我的驱动状态信息")]
class MyDriver_Status
{
[key, read]
string InstanceName;
[read]
uint32 CurrentTemperature;
[read]
uint32 FanSpeed;
[WmiMethodId(1),
Implemented,
Description("重置设备")]
uint32 ResetDevice([in, out] uint32 Status);
};
MOF文件写好后,需要用mofcomp.exe编译注册。我建议把MOF编译步骤放到驱动安装脚本里,省得用户手动操作。
21.3 ETW:内核级事件追踪
ETW是Windows最强大的事件追踪框架。它的开销极低,生产环境可以一直开着。我做过测试,每秒写几万条事件,CPU占用率不到1%。
ETW的核心概念有三个:提供者(Provider)、事件(Event)、消费者(Consumer)。驱动作为提供者发布事件,用户态工具(如Xperf、WPA)作为消费者收集和分析事件。
// ETW事件发布示例
#define MYDRIVER_PROVIDER_GUID \
{0x12345678, 0x1234, 0x1234, \
{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0}}
REGHANDLE MyDriverRegHandle;
// 注册ETW提供者
NTSTATUS EtwRegisterProvider()
{
EVENT_DATA_DESCRIPTOR desc;
ULONG status;
status = EventRegister(
&MYDRIVER_PROVIDER_GUID,
NULL, // 无回调
NULL, // 无上下文
&MyDriverRegHandle
);
return status;
}
// 发布事件
VOID EtwWriteEvent(
_In_ ULONG EventId,
_In_ PCWSTR Message
)
{
EVENT_DATA_DESCRIPTOR desc;
EventDataDescCreate(&desc, Message,
(ULONG)(wcslen(Message) * sizeof(WCHAR)));
EventWrite(
MyDriverRegHandle,
&EventId,
NULL, // 无相关活动ID
1, // 一个数据描述符
&desc
);
}
21.4 性能计数器:实时监控利器
性能计数器(Performance Counter)是Windows性能监视器(PerfMon)的数据来源。驱动可以注册自定义计数器,让管理员通过PerfMon实时查看驱动状态。
实现性能计数器需要注册PerfLib提供者,定义计数器集和计数器对象。我习惯把计数器分成几类:瞬时值(如当前温度)、平均值(如平均延迟)、累加值(如总处理包数)。
| 计数器类型 | 说明 | 示例 |
|---|---|---|
| PERF_COUNTER_RAWCOUNT | 原始计数值 | 当前连接数 |
| PERF_COUNTER_ELAPSED_TIME | 经过时间 | 设备运行时长 |
| PERF_AVERAGE_BULK | 平均值 | 平均I/O延迟 |
| PERF_COUNTER_RATE | 速率 | 每秒处理请求数 |
// 性能计数器数据提供回调
ULONG PerfCounterCallback(
_In_ PVOID Context,
_In_ ULONG Function,
_In_ PVOID Data,
_In_ ULONG DataLength
)
{
PPERF_COUNTER_DATA CounterData =
(PPERF_COUNTER_DATA)Data;
switch (Function)
{
case PERF_COUNTER_GET_DATA:
// 填充计数器数据
CounterData->CurrentValue =
g_DeviceContext->CurrentConnections;
CounterData->TimeStamp =
KeQueryPerformanceCounter(NULL);
break;
}
return STATUS_SUCCESS;
}
21.5 日志记录:最后的防线
日志记录是调试和排错的最后一道防线。WMI和ETW再强大,也有失效的时候(比如系统刚启动、驱动加载阶段)。这时候,传统的日志记录就派上用场了。
我习惯用三层日志策略:
- DbgPrint/DbgPrintEx:调试用,只在Debug版本或开启调试器时输出
- EventLog:系统事件日志,记录关键错误和警告
- 自定义日志文件:记录详细操作日志,方便事后分析
// 三层日志记录实现
VOID LogMessage(
_In_ ULONG Level,
_In_ PCSTR Format,
...
)
{
va_list args;
CHAR buffer[512];
va_start(args, Format);
vsprintf_s(buffer, sizeof(buffer), Format, args);
va_end(args);
// 第一层:调试输出
DbgPrintEx(DPFLTR_IHVDRIVER_ID,
Level,
"[MyDriver] %s\n",
buffer);
// 第二层:关键错误写入事件日志
if (Level <= DPFLTR_ERROR_LEVEL)
{
WriteEventLogEntry(buffer);
}
// 第三层:写入自定义日志文件
WriteToLogFile(buffer);
}
21.6 综合实践:构建完整的监控方案
在实际项目中,我会把这四个组件组合起来使用。举个例子:
- 驱动启动时,用ETW记录初始化过程
- 运行期间,性能计数器暴露实时数据
- WMI提供者暴露管理接口,允许用户查询和配置
- 所有错误和警告写入系统事件日志
- 调试信息通过DbgPrint输出
这样做的好处是:开发阶段用DbgPrint和ETW调试,运维阶段用PerfMon和WMI监控,出问题时查事件日志和自定义日志文件。各司其职,互不干扰。
嗯,关于WMI与事件追踪的内容就讲到这里。这些技术组合起来,能让你的驱动变得"透明"——用户态工具可以随时查看状态,管理员可以实时监控性能,出问题时有日志可查。这才是专业驱动该有的样子。