定时器

来源:互联网 发布:怎样学好历史高中知乎 编辑:程序博客网 时间:2024/05/17 02:46

转载自:http://mzf2008.blog.163.com/blog/static/355997862010112622923357/


1.      I/O定时器

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

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

 

初始化定时器:

NTSTATUS 
  IoInitializeTimer(
    IN PDEVICE_OBJECT
  DeviceObject,
    IN PIO_TIMER_ROUTINE
  TimerRoutine,
    IN PVOID
  Context
    );

 

开启I/O定时器:

VOID 
  IoStartTimer(
    IN PDEVICE_OBJECT
  DeviceObject
    );

 

停止I/O定时器:

VOID 
  IoStopTimer(
    IN PDEVICE_OBJECT
  DeviceObject
    );

 

示例代码:

用户层代码:

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;

}

 

I/O定时器调用例程:

#pragma LOCKEDCODE  //运行在DISPATCH_LEVELIRQL级别

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

}

2010年12月26日 - Fly - 从C开始
 

 

2.      DPC定时器

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

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

 

初始化定时器对象:

VOID 
  KeInitializeTimer(
    IN PKTIMER
  Timer
    );

 

初始化DPC对象:

VOID 
  KeInitializeDpc(
    IN PRKDPC
  Dpc,
    IN PKDEFERRED_ROUTINE
  DeferredRoutine,
    IN PVOID
  DeferredContext
    );

 

开启定时器:

BOOLEAN 
  KeSetTimer(
    IN PKTIMER
  Timer,
    IN LARGE_INTEGER
  DueTime,
    IN PKDPC
  Dpc  OPTIONAL
    );

 

取消定时器:

BOOLEAN 
  KeCancelTimer(
    IN PKTIMER
  Timer
    );

 

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

 

示例代码:

用户层:

ULONG ulMicroSeconds = 3000000;

BOOL bRet = DeviceIoControl(hFile, TEST_TIME_START, &ulMicroSeconds,

sizeof(ULONG), 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;   //符号链接名

         KDPC dpc;                                                       //储存DPC对象

         KTIMER timer;                                                        //储存定时器对象

         LARGE_INTEGER pollTime;                        //记录定时器间隔时间

} DEVICE_EXTENSION, *PDEVICE_EXTENSION;

 

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

}

注意:每次执行KeSetTimer只会触发一次DPC例程。为了能周期的调用DPC例程,应该在DPC例程中再次调用KeSetTimer,并且时间间隔保持不变。

 

 

3.      等待

(1)      使用KeWaitForSingleObject

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

示例代码:

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

}

 

(2)      使用KeDelayExecutionThread

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

示例代码:

#pragma PAGEDCODE

VOID WaitMicroSecond(LONG MicroSeconds)

{

         KdPrint(("进入延时函数!\n"));

         LARGE_INTEGER waitTime = RtlConvertLongToLargeInteger(-10 * MicroSeconds);

         KeDelayExecutionThread(Executive, KernelMode, &waitTime);

         KdPrint(("离开延时函数!\n"));

}

 

(3)      使用KeStallExecutionProcessor

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

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

示例代码:

#pragma PAGEDCODE

VOID WaitMicroSecond(ULONG MicroSeconds)

{

         KdPrint(("延时 %d 微妙!\n", MicroSeconds));

         KeStallExecutionProcessor(MicroSeconds);

         KdPrint(("离开延时函数,线程继续执行!\n"));

}

 

(4)      使用定时器

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

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

示例代码:

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

}

 

 

4.      与时间相关的其他内核函数

获取当前系统时间的内核函数:

VOID 
  KeQuerySystemTime(
    OUT PLARGE_INTEGER
  CurrentTime
    );

 

将系统时间转换为当前时区对应的时间:

VOID 
  ExSystemTimeToLocalTime(
    IN PLARGE_INTEGER
  SystemTime,
    OUT PLARGE_INTEGER
  LocalTime
    );

 

将当前时区的时间转换为系统时间:

VOID 
  ExLocalTimeToSystemTime(
    IN PLARGE_INTEGER
  LocalTime,
    OUT PLARGE_INTEGER
  SystemTime
    );

 

由当前的年月日得到系统时间:

BOOLEAN 
  RtlTimeFieldsToTime(
    IN PTIME_FIELDS
  TimeFields,
    IN PLARGE_INTEGER
  Time
    );

typedef struct TIME_FIELDS {
    CSHORT Year;
    CSHORT Month;
    CSHORT Day;
    CSHORT Hour;
    CSHORT Minute;
    CSHORT Second;
    CSHORT Milliseconds;
    CSHORT Weekday;
} TIME_FIELDS;

由系统时间得到当前的年月日等信息:

VOID 
  RtlTimeToTimeFields(
    IN PLARGE_INTEGER
  Time,
    IN PTIME_FIELDS
  TimeFields
    );

 

示例代码:

#pragma PAGEDCODE

VOID TimeTest()

{

         LARGE_INTEGER systemTime;

         KeQuerySystemTime(&systemTime);

         TIME_FIELDS timeFields;

         RtlTimeToTimeFields(&systemTime, &timeFields);

         KdPrint(("Current year %d", timeFields.Year));

         KdPrint(("Current month %d", timeFields.Month));

         KdPrint(("Current day %d", timeFields.Day));

         KdPrint(("Current hour %d", timeFields.Hour));

         KdPrint(("Current minute %d", timeFields.Minute));

         KdPrint(("Current second %d", timeFields.Second));

         KdPrint(("Current millisecond %d", timeFields.Milliseconds));

         KdPrint(("Current weekday %d", timeFields.Weekday));

}


原创粉丝点击