[普通]内核编程之-系统时间和定时器

作者(passion) 阅读(967次) 评论(0) 分类( 内核开发)

内核编程与应用程序编程一个很重要的不同点在于:应用编程中,多数情况下只需考虑单线程就可以;在内核编程中,绝大多数情况下所写的代码都位于多线程环境中。

获得当前“滴答数”:

获得系统日前和时间往往是为了写日志,获得启动毫秒数则很适合用来做一个随机数的种子。有时也使用时间相关的函数来寻找程序的性能瓶颈。

在Win32开发中,我们使用GetTickCount()函数来返回系统自启动之后经历的毫秒数。在驱动开发中,对应的函数是KeQueryTickCount():

VOID KeQueryTickCount(

         OUT PLARGE_INTEGER TickCount

         );

需要注意的是,返回到TickCount中的并不是一个简单的毫秒数,而是一个“滴答”数,这个数在不同硬件环境中是不一样的,所以,我们需要结合使用另一个函数:

ULONG KeQueryTimeIncrement();

下面的代码可以求得实际的毫秒数:

void ASCEGetTickCount(PULONG msce)

{

         LARGE_INTEGER tickCount;

         ULONG asceInc = KeQueryTimeIncrement();

         KeQueryTickCount(&tickCount);

         tickCount.QuadPart *= asceInc;

         tickCount.QuadPart /= 10000;

         *msce = tickCount.LowPart;

}

 

获得当前系统时间:

使用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 ASCECurTimeStr()

{

         LARGE_INTEGER snow, now;

         TIME_FIELDS nowFIelds;

         static WCHAR timeStr[32] = {0};

         //获得标准时间

         KeQuerySystemTime(&snow);

         //转换为当地时间

         ExSystemTimeToLocalTime(&snow, &now);

         //转换为人们可以理解的时间格式

         RtlTimeToTimeFields(&now, &nowFields);

         //打印到字符串中

         RtlStringCchPrintfW(

                   timeStr,

                   32*2,

                   L”%4d-%2d-%2d %2d-%2d-%2d”,

                   nowFields.Year, nowFields.Month, nowFields.Day,

                   nowFields.Hour, nowFields.Minute, nowFields.Second

         );

         return timeStr;

}

注意:timeStr是静态变量,因此这个函数不具备多线程安全性。

 

使用定时器:

Windows应用程序编程中,当需要定时执行任务时,经常使用SetTimer()函数。在驱动开发中,我们可以使用KeSetTimer():

BOOLEAN KeSetTimer(

         IN PKTIMER Timer,       //定时器

         IN LARGE_INTEGER DueTime,   //延后执行的时间

         IN PKDPC Dpc OPTIONAL  //要执行的回调函数

         );

其中定时器Timer和回调函数Dpc必须初始化,下面代码可以初始化一个Timer:

KTIMER asceTimer;

KeInitializeTimer(&asceTimer);

而初始化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被调用的时候,让用户传入一些参数。

注意:这是一个“延时执行”的过程,而不是一个定时执行的过程。如果想定时反复执行必须在每次CustomDpc函数被调用时,再次调用KeSetTimer,以此保证下次还可以执行。注意:CustomDpc运行在Dispatch中断级。

 

补充中断级知识》:内核代码始终运行在某个“中断级”上,代码并非从头到尾连续执行,而是在某些执行过程中随时可能被打断:异常发生、中断发生、线程切换等。高中断级上运行的代码不会被低中断级上运行的代码中断,主要的中断级从高到低是:Dispatch>APC>Passive。

 

为了完全实现定时器的功能,我们最后自己封装一些结构:

//内部时钟结构

typedef struct ASCE_TIMER_

{

         KDPC dpc;

         KTIMER timer;

         PKDEFERRED_ROUTINE func;

         PVOID privateContext;        //私有上下文

}ASCE_TIMER, *PASCE_TIMER;

//初始化这个结构

void ASCETimerInit(PASCE_TIMER timer, PKDEFERRED_ROUTINE func)

{

         //请注意,我们将回调函数上下文参数设置为timer

         KeInitializeDpc(&timer->dpc, ASCEOnTimer, timer);

         timer->func = func;

         KeInitializeTimer(&timer->timer);

}

 

//让这个结构中的回调函数在毫秒后开始执行

BOOLEAN ASCESetTimer(PASCE_TIMER timer, ULONG msce, PVOID context)

{

         LARGE_INTEGER due;

         //注意时间单位的转换,msce是毫秒

         due.QuadPart = -10000*msce;

         //用户私有上下文

         timer->privateContext = context;

         return KeSetTimer(&timer->timer, due, &timer->dpc);

}

 

//停止执行

VOID ASCEDestroyTimer(PASCE_TIMER timer)

{

         KeCancelTimer(&timer->timer);

}

在回调函数ASCEOnTimer中要获得上下文,必须从timer->privateContext中获得,此外,在ASCEOnTimer中还需要再次调用ASCESetTimer(),保证下次依然得到执行:

VOID ASCEOnTimer(

         IN struct _KDPC *Dpc,

         IN PVOID DeferredContext,

         IN PVOID SystemArgument1,

         IN PVOID SystemArgument2

)

{

         //这里传入的上下文是timer结构,用来下次再启动延时调用

         PASCE_TIMER timer = (PASCE_TIMER)DeferredContext;

         //获得用户上下文

         PVOID asceContext = timer->privateContext;

         //这里做OnTimer要执行的任务

         …-…-

         //再次调用,我们假设每秒执行以此

         ASCESetTimer(timer, 1000, asceContext);

}


« 上一篇:wifi共享上网(至尊版wifi)
« 下一篇:drcom至尊版使用openwrt路由器拨号
在这里写下您精彩的评论
  • 微信

  • QQ

  • 支付宝

返回首页
返回首页 img
返回顶部~
返回顶部 img