概述:内核定时器使用说明
参考文章
- 驱动开发:内核使用IO/DPC定时器 | LyShark®
内核定时器
内核定时器分为两种,即 IO 定时器和 DPC 定时器,一般来说,IO定时器是DDK(Driver Developer Kit)中提供的一种,该定时器可以为间隔为N秒做定时,但如果要实现毫秒级别间隔,微妙级别间隔,就需要用到DPC定时器,如果是秒级别定时基本上两者没有差别。
IO定时器
内核I/O定时器(Kernel I/O Timer)是Windows内核中的一种定时器机制,允许内核或驱动程序在指定的时间间隔内调用一个回调函数。以下是关于内核I/O定时器的详细介绍:
特点
- 精度:内核I/O定时器的精度通常为秒级。
- 依赖设备对象:内核I/O定时器必须依附于一个设备对象。
使用步骤
- 初始化定时器:调用
IoInitializeTimer函数对定时器进行初始化。此函数每个设备对象只能调用一次。 - 启动定时器:调用
IoStartTimer函数使定时器开始运行。 - 停止定时器:调用
IoStopTimer函数关闭定时器。
使用示例
#include <ntifs.h>
#include <wdm.h>
#include <ntstrsafe.h>
LONG count = 0;
// 自定义定时器函数
VOID MyTimerProcess(__in struct _DEVICE_OBJECT *DeviceObject, __in_opt PVOID Context)
{
InterlockedIncrement(&count);
DbgPrint("定时器计数 = %d", count);
}
VOID UnDriver(PDRIVER_OBJECT driver)
{
// 关闭定时器
IoStopTimer(driver->DeviceObject);
// 删除设备
IoDeleteDevice(driver->DeviceObject);
DbgPrint("Uninstall Driver Is OK \n");
}
NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{
DbgPrint("hello DriverEntry \n");
NTSTATUS status = STATUS_UNSUCCESSFUL;
// 定义设备名以及定时器
UNICODE_STRING dev_name = RTL_CONSTANT_STRING(L"");
PDEVICE_OBJECT dev;
status = IoCreateDevice(Driver, 0, &dev_name, FILE_DEVICE_UNKNOWN, FILE_DEVICE_SECURE_OPEN, FALSE, &dev);
if (!NT_SUCCESS(status))
{
return STATUS_UNSUCCESSFUL;
}
else
{
// 初始化定时器并开启
IoInitializeTimer(dev, MyTimerProcess, NULL);
IoStartTimer(dev);
}
Driver->DriverUnload = UnDriver;
return STATUS_SUCCESS;
}注意事项
- 内核I/O定时器的回调函数会在系统线程上下文中执行。
- 如果需要更高精度的定时器,可以考虑使用DPC定时器。
DPC 定时器
内核DPC(Deferred Procedure Call,延迟过程调用)定时器是一种基于DPC机制的定时器,用于在内核中实现高精度的定时任务。以下是关于内核DPC定时器的特点和使用步骤的详细介绍:
特点
- 高精度:DPC定时器可以实现毫秒甚至微秒级别的定时。
- 延迟执行:DPC定时器允许系统在未来的某个时间点执行任务,而不是立即执行,这有助于避免中断处理例程(ISR)执行时间过长。
- 中断上下文之外执行:DPC定时器的执行不在中断服务例程的上下文中,避免了在ISR中执行过长时间的任务,从而提高系统的响应性和稳定性。
- 任务调度:DPC定时器允许内核将需要延迟执行的任务排队,并在指定的时间点执行这些任务。
使用步骤
-
初始化定时器对象: 使用
KeInitializeTimer函数初始化一个KTIMER对象。c复制
VOID KeInitializeTimer(PKTIMER Timer); -
初始化DPC对象: 使用
KeInitializeDpc函数初始化一个KDPC对象,并设置与DPC关联的定时器例程。c复制
VOID KeInitializeDpc(PRKDPC Dpc, PKDEFERRED_ROUTINE DeferredRoutine, PVOID DeferredContext); -
设置定时器: 使用
KeSetTimer函数设置定时器的触发时间,并将DPC对象与定时器关联。c复制
BOOLEAN KeSetTimer(PKTIMER Timer, LARGE_INTEGER DueTime, PKDPC Dpc);Timer是定时器对象的指针。DueTime表示定时器触发的时间间隔。Dpc是DPC对象的指针。
-
取消定时器: 如果需要取消定时器,可以调用
KeCancelTimer函数。c复制
BOOLEAN KeCancelTimer(PKTIMER Timer); -
周期性触发: 如果需要周期性触发DPC例程,需要在DPC例程中再次调用
KeSetTimer函数。
示例代码
以下是一个简单的DPC定时器使用示例:
c复制
#include <ntifs.h>
#include <wdm.h>
LONG count = 0;
KTIMER g_ktimer;
KDPC g_kdpc;
// 自定义DPC例程
VOID MyTimerProcess(__in struct _KDPC *Dpc, __in_opt PVOID DeferredContext, __in_opt PVOID SystemArgument1, __in_opt PVOID SystemArgument2)
{
LARGE_INTEGER la_dutime = { 0 };
la_dutime.QuadPart = 1000 * 1000 * -10; // 每隔1秒执行一次
// 递增计数器
InterlockedIncrement(&count);
DbgPrint("DPC 定时执行 = %d", count);
// 再次设置定时器
KeSetTimer(&g_ktimer, la_dutime, &g_kdpc);
}
VOID UnDriver(PDRIVER_OBJECT driver)
{
// 取消定时器
KeCancelTimer(&g_ktimer);
DbgPrint("Uninstall Driver Is OK \n");
}
NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{
LARGE_INTEGER la_dutime = { 0 };
la_dutime.QuadPart = 1000 * 1000 * -10; // 每隔1秒执行一次
// 初始化定时器对象
KeInitializeTimer(&g_ktimer);
// 初始化DPC对象
KeInitializeDpc(&g_kdpc, MyTimerProcess, NULL);
// 设置定时器并开始计时
KeSetTimer(&g_ktimer, la_dutime, &g_kdpc);
Driver->DriverUnload = UnDriver;
return STATUS_SUCCESS;
}注意事项
- 在DPC例程中,需要再次调用
KeSetTimer以实现周期性触发。 - 如果需要停止DPC定时器,必须调用
KeCancelTimer,否则可能导致系统不稳定。
获取系统精确时间
在内核中通过 KeQuerySystemTime 获取是的系统时间是标准时间(GMT+0),转换成本地时间还需使用RtlTimeToTimeFields函数将其转换为TIME_FIELDS结构体格式。
相关代码如下所示:
#include <ntifs.h>
#include <wdm.h>
#include <ntstrsafe.h>
/*
typedef struct TIME_FIELDS
{
CSHORT Year;
CSHORT Month;
CSHORT Day;
CSHORT Hour;
CSHORT Minute;
CSHORT Second;
CSHORT Milliseconds;
CSHORT Weekday;
} TIME_FIELDS;
*/
// 内核中获取时间
VOID MyGetCurrentTime()
{
LARGE_INTEGER CurrentTime;
LARGE_INTEGER LocalTime;
TIME_FIELDS TimeFiled;
// 得到格林威治时间
KeQuerySystemTime(&CurrentTime);
// 转成本地时间
ExSystemTimeToLocalTime(&CurrentTime, &LocalTime);
// 转换为TIME_FIELDS格式
RtlTimeToTimeFields(&LocalTime, &TimeFiled);
DbgPrint("[时间与日期] %4d年%2d月%2d日 %2d时%2d分%2d秒",
TimeFiled.Year, TimeFiled.Month, TimeFiled.Day,
TimeFiled.Hour, TimeFiled.Minute, TimeFiled.Second);
}
VOID UnDriver(PDRIVER_OBJECT driver)
{
DbgPrint(("Uninstall Driver Is OK \n"));
}
NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{
MyGetCurrentTime();
DbgPrint("hello lyshark \n");
Driver->DriverUnload = UnDriver;
return STATUS_SUCCESS;
}