驱动编程基础教程第五章:时间与定时器 - Aliwy编程空间[http://www.ntd...

来源:百度文库 编辑:神马文学网 时间:2024/04/27 01:31:38
第五章 时间与定时器
5.1 获得当前滴答数
在编程中,获得当前的系统日期和时间,或者是获得一个从启动开始的毫秒数,是很常见的需求。获得系统日期和时间往往是为了写日志。获得启动毫秒数很适合用来做一个随机数的种子。有时也使用时间相关的函数来寻找程序的性能瓶颈。
熟悉Win32应用程序开发的读者会知道有一个函数GetTickCount(),这个函数返回系统自启动之后经历的毫秒数。在驱动开发中有一个对应的函数KeQueryTickCount(),这个函数的原型如下:
VOID 
KeQueryTickCount(
  OUT PLARGE_INTEGER  TickCount
);
遗憾的是,被返回到TickCount中的并不是一个简单的毫秒数。这是一个“滴答”数。但是一个“滴答”到底为多长的时间,在不同的硬件环境下可能有所不同。为此,必须结合另一个函数使用。下面这个函数获得一个“滴答”的具体的100纳秒数。
ULONG 
  KeQueryTimeIncrement(
  );
得知以上的关系之后,下面的代码可以求得实际的毫秒数:
void MyGetTickCount (PULONG msec)
{
  LARGE_INTEGER tick_count;
  ULONG myinc = KeQueryTimeIncrement();
  KeQueryTickCount(&tick_count);
  tick_count.QuadPart *= myinc;
  tick_count.QuadPart /=  10000;
  *msec = tick_count.LowPart;
}
这不是一个简单的过程。不过所幸的是,现在有代码可以拷贝了。
5.2 获得当前系统时间
接下来的一个需求是得到当前的可以供人类理解的时间。包括年、月、日、时、分、秒这些要素。在驱动中不能使用诸如CTime之类的MFC类。不过与之对应的有TIME_FIELDS,这个结构中含有对应的时间要素。
KeQuerySystemTime()得到当前时间。但是得到的并不是当地时间,而是一个格林威治时间。之后请使用ExSystemTimeToLocalTime()转换可以当地时间。这两个函数的原型如下:
VOID 
KeQuerySystemTime(
OUT PLARGE_INTEGER  CurrentTime
);
VOID
ExSystemTimeToLocalTime(
IN PLARGE_INTEGER  SystemTime,
OUT PLARGE_INTEGER  LocalTime
);
这两个函数使用的“时间”都是长长整型数据结构。这不是人类可以阅读的。必须通过函数RtlTimeToTimeFields转换为TIME_FIELDS。这个函数原型如下:
VOID 
RtlTimeToTimeFields(
IN PLARGE_INTEGER  Time,
IN PTIME_FIELDS  TimeFields
);
读者需要实际应用一下来加深印象。下面写出一个函数:这个函数返回一个字符串。这个字符串写出当前的年、月、日、时、分、秒,这些数字之间用“-”号隔开。这是一个很有用的函数。而且同时用到上面三个函数,此外,请读者回忆前面关于字符串的打印的相关章节。
PWCHAR MyCurTimeStr()
{
  LARGE_INTEGER snow,now;
  TIME_FIELDS now_fields;
  static WCHAR time_str[32] = { 0 };
  // 获得标准时间
  KeQuerySystemTime(&snow);
  // 转换为当地时间
  ExSystemTimeToLocalTime(&snow,&now);
  // 转换为人类可以理解的时间要素
  RtlTimeToTimeFields(&now,&now_fields);
  // 打印到字符串中
  RtlStringCchPrintfW(
  time_str,
  32*2,
  L"%4d-%2d-%2d %2d-%2d-%2d",
  now_fields.Year,now_fields.Month,now_fields.Day,
  now_fields.Hour,now_fields.Minute,now_fields.Second);
  return time_str;
}
请注意time_str是静态变量。这使得这个函数不具备多线程安全性。请读者考虑一下,如何保证多个线程同时调用这个函数的时候,不出现冲突?
5.3 使用定时器
使用过Windows应用程序编程的读者的读者一定对SetTimer()映像尤深。当需要定时执行任务的时候,SetTimer()变得非常重要。这个功能在驱动开发中可以通过一些不同的替代方法来实现。比较经典的对应是KeSetTimer(),这个函数的原型如下:
BOOLEAN
KeSetTimer(
  IN PKTIMER  Timer,        // 定时器
  IN LARGE_INTEGER  DueTime,// 延后执行的时间
  IN PKDPC  Dpc  OPTIONAL   // 要执行的回调函数结构
);
其中的定时器Timer和要执行的回调函数结构Dpc都必须先初始化。其中Timer的初始化比较简单。下面的代码可以初始化一个Timer:
KTIMER my_timer;
KeInitializeTimer(&my_timer);
Dpc的初始化比较麻烦。这是因为需要提供一个回调函数。初始化Dpc的函数原型如下:
VOID
KeInitializeDpc(
  IN PRKDPC  Dpc,
  IN PKDEFERRED_ROUTINE  DeferredRoutine,
  IN PVOID  DeferredContext
);
PKDEFERRED_ROUTINE这个函数指针类型所对应的函数的类型实际上是这样的:
VOID
CustomDpc(
  IN struct _KDPC  *Dpc,
  IN PVOID  DeferredContext,
  IN PVOID  SystemArgument1,
  IN PVOID  SystemArgument2
);
读者需要关心的只是DeferredContext。这个参数是KeInitializeDpc调用时传入的参数。用来提供给CustomDpc被调用的时候,让用户传入一些参数。
至于后面的SystemArgument1和SystemArgument2则请不要理会。Dpc是回调这个函数的KDPC结构。
请注意这是一个“延时执行”的过程。而不是一个定时执行的过程。因此每次执行了之后,下次就不会再被调用了。如果想要定时反复执行,就必须在每次CustomDpc函数被调用的时候,再次调用KeSetTimer,来保证下次还可以执行。
值得注意的是,CustomDpc将运行在APC中断级。因此并不是所有的事情都可以做(在调用任何内核系统函数的时候,请注意WDK说明文档中标明的中断级要求。)
这些事情非常的烦恼,因此要完全实现定时器的功能,需要自己封装一些东西。下面的结构封装了全部需要的信息:
// 内部时钟结构
typedef struct MY_TIMER_
{
  KDPC dpc;
  KTIMER timer;
  PKDEFERRED_ROUTINE func;
  PVOID private_context;
} MY_TIMER,*PMY_TIMER;
// 初始化这个结构:
void MyTimerInit(PMY_TIMER timer, PKDEFERRED_ROUTINE func)
{
  // 请注意,我把回调函数的上下文参数设置为timer,为什么要
  // 这样做呢?
  KeInitializeDpc(&timer->dpc,sf_my_dpc_routine,timer);
  timer->func = func;
  KeInitializeTimer(&timer->timer);
  return (wd_timer_h)timer;
}
// 让这个结构中的回调函数在n毫秒之后开始运行:
BOOLEAN MyTimerSet(PMY_TIMER timer,ULONG msec,PVOID context)
{
  LARGE_INTEGER due;
  // 注意时间单位的转换。这里msec是毫秒。
  due.QuadPart = -10000*msec;
  // 用户私有上下文。
  timer->private_context = context;
  return KeSetTimer(&timer->timer,due,&mytimer->dpc);
};
  
// 停止执行
VOID MyTimerDestroy(PMY_TIMER timer)
{
  KeCancelTimer(&mytimer->timer);
};
使用结构PMY_TIMER已经比结合使用KDPC和KTIMER简便许多。但是还是有一些要注意的地方。真正的OnTimer回调函数中,要获得上下文,必须要从timer->private_context中获得。此外,OnTimer中还有必要再次调用MyTimerSet(),来保证下次依然得到执行。
VOID
MyOnTimer (
  IN struct _KDPC  *Dpc,
  IN PVOID  DeferredContext,
  IN PVOID  SystemArgument1,
  IN PVOID  SystemArgument2
)
{
  // 这里传入的上下文是timer结构,用来下次再启动延时调用
  PMY_TIMER timer = (PMY_TIMER)DeferredContext;
  // 获得用户上下文
  PVOID my_context = timer->private_context;
  // 在这里做OnTimer中要做的事情
  ……
  // 再次调用。这里假设每1秒执行一次
  MyTimerSet(timer,1000,my_context);
};
关于定时器就介绍到这里了。