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

文章目录
  1. 1. 内核定时器
    1. 1.1. IO定时器
    2. 1.2. 特点
    3. 1.3. 使用步骤
    4. 1.4. 使用示例
    5. 1.5. 注意事项
  2. 2. DPC 定时器
    1. 2.1. 特点
    2. 2.2. 使用步骤
    3. 2.3. 示例代码
    4. 2.4. 注意事项
  3. 3. 获取系统精确时间

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

参考文章

内核定时器

内核定时器分为两种,即 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;
}