概述:内核定时器使用说明

参考文章

内核定时器

内核定时器分为两种,即 IO 定时器和 DPC 定时器,一般来说,IO定时器是DDK(Driver Developer Kit)中提供的一种,该定时器可以为间隔为N秒做定时,但如果要实现毫秒级别间隔,微妙级别间隔,就需要用到DPC定时器,如果是秒级别定时基本上两者没有差别。

IO定时器

内核I/O定时器(Kernel I/O Timer)是Windows内核中的一种定时器机制,允许内核或驱动程序在指定的时间间隔内调用一个回调函数。以下是关于内核I/O定时器的详细介绍:

特点

  • 精度:内核I/O定时器的精度通常为秒级。
  • 依赖设备对象:内核I/O定时器必须依附于一个设备对象。

使用步骤

  1. 初始化定时器:调用IoInitializeTimer函数对定时器进行初始化。此函数每个设备对象只能调用一次。
  2. 启动定时器:调用IoStartTimer函数使定时器开始运行。
  3. 停止定时器:调用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定时器的特点和使用步骤的详细介绍:

特点

  1. 高精度:DPC定时器可以实现毫秒甚至微秒级别的定时。
  2. 延迟执行:DPC定时器允许系统在未来的某个时间点执行任务,而不是立即执行,这有助于避免中断处理例程(ISR)执行时间过长。
  3. 中断上下文之外执行:DPC定时器的执行不在中断服务例程的上下文中,避免了在ISR中执行过长时间的任务,从而提高系统的响应性和稳定性。
  4. 任务调度:DPC定时器允许内核将需要延迟执行的任务排队,并在指定的时间点执行这些任务。

使用步骤

  1. 初始化定时器对象: 使用KeInitializeTimer函数初始化一个KTIMER对象。

    c复制

    VOID KeInitializeTimer(PKTIMER Timer);
  2. 初始化DPC对象: 使用KeInitializeDpc函数初始化一个KDPC对象,并设置与DPC关联的定时器例程。

    c复制

    VOID KeInitializeDpc(PRKDPC Dpc, PKDEFERRED_ROUTINE DeferredRoutine, PVOID DeferredContext);
  3. 设置定时器: 使用KeSetTimer函数设置定时器的触发时间,并将DPC对象与定时器关联。

    c复制

    BOOLEAN KeSetTimer(PKTIMER Timer, LARGE_INTEGER DueTime, PKDPC Dpc);
    • Timer是定时器对象的指针。
    • DueTime表示定时器触发的时间间隔。
    • Dpc是DPC对象的指针。
  4. 取消定时器: 如果需要取消定时器,可以调用KeCancelTimer函数。

    c复制

    BOOLEAN KeCancelTimer(PKTIMER Timer);
  5. 周期性触发: 如果需要周期性触发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;
}