Windows内核编程中的KeGenericCallDpc函数
KeGenericCallDpc 是 Windows 内核中一个强大但未公开文档化(Undocumented) 的函数。它的核心作用是向系统上的每一个逻辑处理器(CPU核心)广播一个延迟过程调用(DPC),让指定的回调函数在每个CPU核心上都执行一次。
函数声明
c
//
// 注意:WDK 10.0.28000.0 的 km 头文件中缺少 KeGenericCallDpc 的声明
//(SDK 头文件缺陷),但该 API 仍由 ntoskrnl 正常导出。
// 此处补充与官方 WDK 签名一致的 extern 声明以通过编译;
// 未来若头文件恢复声明,重复的函数声明在 C++ 中合法,不影响编译。
//
extern "C"
NTKERNELAPI
BOOLEAN
KeGenericCallDpc(
_In_ PKDEFERRED_ROUTINE Routine,
_In_opt_ PVOID Context
);
工作原理
如何广播DPC
- 获取处理器数量:函数内部会调用 KeQueryActiveProcessorCountEx(ALL_PROCESSOR_GROUPS) 来获取当前系统中所有活跃逻辑处理器的数量。
- 为每个CPU调度DPC:它会为每一个逻辑处理器分别初始化并插入一个DPC。当这些DPC在各自的CPU核心上被调度执行时,Routine 回调函数就会被调用。
- 并行执行:回调函数会在所有CPU上几乎同时、并行地运行。
同步机制
由于回调函数在所有CPU上并行执行,调用 KeGenericCallDpc 的原始线程需要一种方法来等待所有CPU上的工作完成。为此,必须配合使用另外两个同样未公开文档化的函数:
- KeSignalCallDpcSynchronize:在你的回调函数(Routine)中,必须在某个时刻调用此函数。它的作用是在所有处理器之间建立一个同步屏障(Barrier),确保所有并发的回调执行到此处时能进行同步。
- KeSignalCallDpcDone:在所有回调函数都完成了必要的同步工作后,再由原始调用线程调用此函数。它用于向原始线程发出信号,表明所有CPU上的DPC均已处理完毕。
一个典型的使用流程如下:
- 调用 KeGenericCallDpc 广播DPC。
- 在每个CPU的回调函数中,执行核心任务后,调用 KeSignalCallDpcSynchronize。
- 当所有CPU都调用了 KeSignalCallDpcSynchronize 后,原始调用线程可以调用 KeSignalCallDpcDone。
- 此时,原始线程可以安全地继续执行,因为所有CPU上的工作都已完成。
注意事项
回调函数在DPC级别(IRQL = DISPATCH_LEVEL) 运行,这要求代码必须位于非分页内存(Non-paged pool) 中,并且不能执行任何可能导致阻塞或等待的操作。
总的来说,KeGenericCallDpc 是一个强大但危险性极高的内核工具。鉴于其未公开和不稳定的特性,在开发生产级驱动程序时强烈不建议使用。如果确实有需求,应优先考虑使用其公开的替代方案 KeIpiGenericCall。