【驱动学习】8-内核使用IO/DPC定时器

文章目录

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

参考文章

内核定时器

内核定时器分为两种,即 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函数关闭定时器。

使用示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#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复制

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

    c复制

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

    c复制

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

    c复制

    1
    BOOLEAN KeCancelTimer(PKTIMER Timer);
  5. 周期性触发: 如果需要周期性触发DPC例程,需要在DPC例程中再次调用KeSetTimer函数。

示例代码

以下是一个简单的DPC定时器使用示例:

c复制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#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结构体格式。

相关代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#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;
}