I/O定时器和DPC定时器

1.I/O定时器

I/O定时器是DDK提供的一种定时器,使用这种定时器时,每间隔1S钟系统会调用一次I/O定时器例程。

I/O定时器可以为间隔N秒做定时,但是如果要实现毫秒级别间隔,微妙级别间隔,就需要用到DPC定时器。

初始化定时器:IoInitializeTimer

开启I/O定时器:IoStartTimer

停止I/O定时器: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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
用户层代码:
BOOL bRet = DeviceIoControl(hFile, TEST_TIME_START, NULL, 0, NULL, 0, &dwRet, NULL);
Sleep(10000);
bRet = DeviceIoControl(hFile, TEST_TIME_STOP, NULL, 0, NULL, 0, &dwRet, NULL);
 
定义设备扩展:
typedef struct _DEVICE_EXTENSION {
         PDEVICE_OBJECT pDevice;
         UNICODE_STRING ustrDeviceName;      //设备名称
         UNICODE_STRING ustrSymLinkName;   //符号链接名
         LONG ulTimeCount;                                     //间隔时间
} DEVICE_EXTENSION, *PDEVICE_EXTENSION;
 
在DriverEntry中初始化I/O计时器:
#pragma INITCODE
extern "C" NTSTATUS DriverEntry (
                            IN PDRIVER_OBJECT pDriverObject,
                            IN PUNICODE_STRING pRegistryPath    )
{
         。。。
         IoInitializeTimer(pDevObj, TimeProc, NULL);
         。。。
}
 
IRP_MJ_DEVICE_CONTROL派遣函数
#pragma PAGEDCODE
NTSTATUS HelloDDKDeviceControl(IN PDEVICE_OBJECT pDevObj,IN PIRP pIrp)
{
         NTSTATUS status = STATUS_SUCCESS;
         KdPrint(("进入HelloDDKDeviceControl处理函数!\n"));
         PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(pIrp);
         ULONG ulOut = stack->Parameters.DeviceIoControl.OutputBufferLength;
         ULONG ulIn = stack->Parameters.DeviceIoControl.InputBufferLength;
         ULONG code = stack->Parameters.DeviceIoControl.IoControlCode;
 
         PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION)pDevObj->DeviceExtension;
         switch (code)
         {
                   case TEST_TIME_START:
                   {
                            KdPrint(("开启I/O计数器!\n"));
                            pdx->ulTimeCount = 3;  
                            //开启I/O定时器  
                            IoStartTimer(pDevObj);
                            break;
                   }
                   case TEST_TIME_STOP:
                   {
                            KdPrint(("停止I/O计时器!\n"));
                            //停止I/O定时器
                            IoStopTimer(pDevObj);
                            break;
                   }
                   default:
                            status = STATUS_INVALID_VARIANT;
         }
         pIrp->IoStatus.Information = 0;
         pIrp->IoStatus.Status = status;
         IoCompleteRequest(pIrp, IO_NO_INCREMENT);
         return status;
}

#pragma LOCKEDCODE  //运行在DISPATCH_LEVEL的IRQL级别
VOID TimeProc(IN PDEVICE_OBJECT DeviceObject, IN PVOID Context)
{
         PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION)DeviceObject->DeviceExtension;
         KdPrint(("进入I/O计时器处理函数!\n"));
         //递减计数
         InterlockedDecrement(&pdx->ulTimeCount);
         //当计数减到0之后,继续变为3,
         LONG preCount = InterlockedCompareExchange(&pdx->ulTimeCount, 3, 0);
         //每隔3秒,计数器一个循环,输出如下信息
         if (preCount == 0)
         {
                   KdPrint(("3秒钟过去了!\n"));
         }
         //证明该线程运行在任意线程上下文
         PEPROCESS pEProcess = IoGetCurrentProcess();
         PWSTR ProcessName = (PWSTR)((ULONG)pEProcess + 0x174);
         KdPrint(("当前运行中的进程:%s", ProcessName));
}

2.DPC定时器

这种定时器更加灵活,可以对任意间隔时间进行定时。DPC定时器内部使用定时器对象KTIMER,当对定时器设定一个时间间隔后,每隔这段时间操作系统会将一个DPC例程插入DPC队列。当操作系统读取DPC队列时,对应的DPC例程会被执行。DPC定时器例程相当于定时器的回调函数。

在使用DPC定时器前,需要初始化DPC对象和定时器对象。

初始化定时器对象:KeInitializeTimer

初始化DPC对象:KeInitializeDpc

开启定时器:KeSetTimer

取消定时器:KeCancelTimer

注意;在调用KeSetTimer后,只会触发一次DPC例程。如果想周期的触发DPC例程,需要在DPC例程被触发后,再次调用KeSetTimer函数。

示例代码:

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
54
55
在DriverEntry中初始化:
KeInitializeTimer(&pDevExt->timer);
KeInitializeDpc(&pDevExt->dpc, DPCFunction, pDevObj);
 
IRP_MJ_DEVICE_CONTROL派遣函数:
#pragma PAGEDCODE
NTSTATUS HelloDDKDeviceControl(IN PDEVICE_OBJECT pDevObj,IN PIRP pIrp)
{
         NTSTATUS status = STATUS_SUCCESS;
         KdPrint(("进入HelloDDKDeviceControl处理函数!\n"));
         PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(pIrp);
         ULONG ulOut = stack->Parameters.DeviceIoControl.OutputBufferLength;
         ULONG ulIn = stack->Parameters.DeviceIoControl.InputBufferLength;
         ULONG code = stack->Parameters.DeviceIoControl.IoControlCode;
         PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION)pDevObj->DeviceExtension;
         switch (code)
         {
                   case TEST_TIME_START:
                   {
                            KdPrint(("开启I/O计数器!\n"));
                            ULONG ulMicroSeconds = *(PULONG)pIrp->AssociatedIrp.SystemBuffer;
                            pdx->pollTime = RtlConvertLongToLargeInteger(-10 * ulMicroSeconds);
                            KeSetTimer(&pdx->timer, pdx->pollTime, &pdx->dpc);
                            break;
                   }
                   case TEST_TIME_STOP:
                   {
                            KdPrint(("停止I/O计时器!\n"));
                            KeCancelTimer(&pdx->timer);
                            break;
                   }
                   default:
                            status = STATUS_INVALID_VARIANT;
         }
         pIrp->IoStatus.Information = 0;
         pIrp->IoStatus.Status = status;
         IoCompleteRequest(pIrp, IO_NO_INCREMENT);
         return status;
}

DPC派遣函数:
VOID DPCFunction(
                     IN struct _KDPC  *Dpc,
                     IN PVOID  DeferredContext,
                     IN PVOID  SystemArgument1,
                     IN PVOID  SystemArgument2
                     )
{
         PDEVICE_OBJECT pDevObj = (PDEVICE_OBJECT)DeferredContext;
         PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION)pDevObj->DeviceExtension;
         KeSetTimer(&pdx->timer, pdx->pollTime, &pdx->dpc);
         PEPROCESS pEProcess = IoGetCurrentProcess();
         PWSTR ProcessName = (PWSTR)((ULONG)pEProcess + 0x174);
         KdPrint(("ProcessName:%s", ProcessName));
}

3.等待

①使用KeWaitForSingleObject

首先初始化一个内核同步对象,其初始化状态为未激发。然后调用KeWaitForSingleObject,并对其设置timeout参数,该参数是需要等待的时间。

示例代码:

1
2
3
4
5
6
7
8
9
10
#pragma PAGEDCODE
VOID WaitMicroSecond(LONG MicroSeconds)
{
         KdPrint(("进入延时函数!\n"));
         KEVENT enent;
         KeInitializeEvent(&enent, SynchronizationEvent, FALSE);
         LARGE_INTEGER waitTime = RtlConvertLongToLargeInteger(-10 * MicroSeconds);
         KeWaitForSingleObject(&enent, Executive,KernelMode, FALSE, &waitTime);
         KdPrint(("离开延时函数!\n"));
}

②使用KeDelayExecutionThread

该内核函数和KeWaitForSingleObject类似,都是强制当前线程进入睡眠状态。经过指定的睡眠时间后,线程恢复运行。

示例代码:

1
2
3
4
5
6
7
8
#pragma PAGEDCODE
VOID WaitMicroSecond(LONG MicroSeconds)
{
         KdPrint(("进入延时函数!\n"));
         LARGE_INTEGER waitTime = RtlConvertLongToLargeInteger(-10 * MicroSeconds);
         KeDelayExecutionThread(Executive, KernelMode, &waitTime);
         KdPrint(("离开延时函数!\n"));
}

③使用KeStallExecutionProcessor

该内核函数是让CPU处于忙等待状态,而不是处于睡眠,类似于自旋锁。经过指定时间后,继续让线程运行。

因为这种方法浪费宝贵的CPU时间,因此DDK文档规定,此函数不宜超过50微妙。由于没有将线程进入睡眠状态,也不会发生进程间的切换,因此这种方法的延时比较精确。

示例代码:

1
2
3
4
5
6
7
#pragma PAGEDCODE
VOID WaitMicroSecond(ULONG MicroSeconds)
{
         KdPrint(("延时 %d 微妙!\n", MicroSeconds));
         KeStallExecutionProcessor(MicroSeconds);
         KdPrint(("离开延时函数,线程继续执行!\n"));
}

④使用定时器

这里用到的定时器对象和前面讲的DPC定时器略有不同,这里没有用到DPC对象和DPC例程,因此当指定时间到后,不会进入DPC例程。

定时器对象和其他内核同步对象一样,也是有两个状态,一个是未激发状态,一个是激发状态。当初始化定时器的时候,定时器处于未激发状态。当使用KeSetTimer后,经过指定时间后,进入激发状态。这样就可以是用KeWaitForSingleObject函数对定时器对象进行等待。

示例代码:

1
2
3
4
5
6
7
8
9
10
11
#pragma PAGEDCODE
VOID WaitMicroSecond(ULONG MicroSeconds)
{
         KdPrint(("延时 %d 微妙!\n", MicroSeconds));
         KTIMER timer;
         KeInitializeTimer(&timer);
         LARGE_INTEGER waitTime = RtlConvertLongToLargeInteger(-10 * MicroSeconds);
         KeSetTimer(&timer, waitTime, NULL);
         KeWaitForSingleObject(&timer, Executive, KernelMode, FALSE, NULL);
         KdPrint(("离开延时函数,线程继续执行!\n"));
}

本文链接:http://www.alonemonkey.com/kernel-io-dpc.html