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")); } |