《Windows内核编程》---系统时间和定时器

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

获得当前“滴答数”:

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

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

}

你可能感兴趣的:(windows)