【转】OAL之系统时钟
1. 系统时钟与内核的关系 WinCE 5.0采用基于时间片的抢占式多任务的实时内核,而且每个线程可以根据需要自行设定线程时间片的大小(参考CeSetThreadQuantum函数),默认为100ms,这个默认值dwDefaultThreadQuantum也可以在OEMInit()时自行设定。在内核源文件中,与单词Quantum有关的变量名一般是指时间片,WinCE内核定义了几个与时钟有关的全局变量,他们也是内核与OAL接口的一部分: 1) dwReschedTime,这个变量在内核的调度程序中更新,KCNextThread()是内核调度程序的一部分,它根据线程的优先级和状态决定接下来处于运行状态的线程,并根据该线程的时间片来设定dwReschedTime。顾名思义,这个变量就是指下一次运行调度程序的时间。 既然讲的是WinCE 5.0的内核,这里就不得不插入介绍一下它的调度程序有一个新特性叫做可变的系统时钟节拍(system tick),目的就是让硬件定时器(CPU的时钟)只有在调度程序需要时才产生中断。这样不但可以提高调度程序的效率,而且有助于CPU节能(有关这点后面还会讲到)。这个特性是可选的,依赖与OAL的实现。不采用这个特性,一般每个系统时钟节拍为1ms,即每毫秒产生一个时钟中断。当然,就像默认的线程时间片一样,这个值也是可以在OEMInit()时自行设定。 2) CurMSec,DWORD类型变量,每次时钟中断时更新,累计从系统启动到当前一共经历了多少系统时钟节拍(太绕口了,或者说系统嘀哒(tick)了多少下)。 如果用户程序调用GetTickCount()函数,其实就是直接返回这个变量的值。如上所述可知,当系统的时钟节拍(system tick)为1ms时,CurMSec就是毫秒级的计数器,GetTickCount()的精度就不会超过1ms。 在时钟中断处理函数(TimerISR)中除了要更新CurMSec,还要根据dwReschedTime来判断是否到了运行调度程序的时间: if ((int)(CurMSec - dwReschedTime) >= 0) sysIntr = SYSINTR_RESCHED; 3) curridlehigh, curridlelow, idleconv 这几个变量实现了一个64位的计数器,反映了系统处于空闲模式(Idle mode)的时间。一般在OEMIdle()函数内更新。用户程序通过调用GetIdleTime()函数可以得到这个值。 这里再插入一些与OEMIdle有关的知识点: 调度算法与电源管理有紧密的联系,也就是与现阶段中央提倡的节能减排有一定的联系因为绝大多数的移动设备都是希望节能的,所以当所有线程处于阻塞状态时,内核就会通过调用OEMIdle()将CPU置于空闲模式(低功耗状态)。这种状态直到有中断发生或者有线程可运行时为止。实际上,许多设备都是要花大量时间等待用户的输入,因此如果CPU支持低功耗模式,就应该在OEMIdle函数中实现之。 如果不采用前面提到的那个新的调度程序特性(Variable Tick Scheduler),在OEMIdle函数中还需要判断Idle的准确时间段,而可变的系统时间节拍可以事先设置好正确的时钟中断,这样OEMIdle函数就可以更快的将CPU置于低功耗状态,这也就是说它节能的理由。 有时需要计算一段程序运行时的CPU占用率,可以使用如下代码: dwStartTick = GetTickCount(); dwIdleSt = GetIdleTime(); Sleep(); // 被测代码段 dwStopTick = GetTickCount(); dwIdleEd = GetIdleTime(); PercentIdle = ((100*(dwIdleEd - dwIdleSt)) / (dwStopTick - dwStartTick)); 这段代码见MSDN中GetIdleTime一节,1-PercentIdle即为CPU占用率。 2. 系统时钟模块结构 这篇文章是以PQOAL(Production-Quality OAL)结构介绍系统时钟模块的实现,PQOAL是WinCE 5.0开始才有的东东,它可以简化OAL代码的实现,并有利于OAL代码的移植。简单分析一下目录结构: %_WINCEROOT%/Platform/Common 为硬件无关以及CPU相关的PQOAL代码; %_WINCEROOT%/Platform/Common/Src/Common 为硬件无关的代码,下面以黄色背景列举这类函数; %_WINCEROOT%/Platform/Common/Src/$CPU$ 为CPU相关代码,下面以绿色背景列举这类函数。 如果你的平台所采用的CPU在这个目录中已经存在,比如pxa270相关代码就在%_WINCEROOT%/Platform/Common/Src/ARM/Intel/PXA27x目录下,一般情况下不需要做任何修改,这些代码就可以完成大部分OAL的功能。单就pxa270的系统时钟模块而言,不需要自己写任何代码也可以使用,但这部分代码其实没有实现WinCE 5.0才有的那个新特性(Variable Tick Scheduler)。尤其是OALCPUIdle()的默认实现只是简单的循环,最好能自行实现这个函数,使CPU置于低功耗状态。 在OAL中,与系统时钟相关的数据结构,变量和函数接口在Oal_timer.h头文件中都有其定义或者注释。 (%_WINCEROOT%/Platform/Common/Src/Inc/Oal_timer.h) 2.1 与内核进行交互的变量和函数 2.1.1 UINT32 OALTimerIntrHandler(); 这个就是前面提到的TimerISR,即时钟中断处理函数,返回一个SYSINTR_的值。 2.1.2 CurMSec,前面已经提到,定义在内核源文件中。 2.1.3 OEMIdle(),前面已经提到,已有PQOAL实现,其内部会调用OALCPUIdle()将CPU置于空闲模式(低功耗状态)。 2.1.4 SC_GetTickCount() 这个是内核需要的毫秒级定时器,返回从系统启动开始算起的毫秒数。如果系统时钟节拍为1ms,那么直接返回CurMSec即可,否则如果系统时钟节拍超过1ms,则需要高精度计数器来调整到一个毫秒的时间间隔。 该函数已经由PQOAL实现,但它会调用与硬件相关的函数OALTimerCountsSinceSysTick()。 2.1.5 pQueryPerformanceFrequency, pQueryPerformanceCounter 内核中定义的函数指针,通过这两个函数实现高精度的计时器。 这两个函数的原型也已经由PQOAL实现,即OALTimerQueryPerformanceFrequency和OALTimerQueryPerformanceCounter,也会调用与硬件相关的函数OALTimerCountsSinceSysTick()和OAL层变量g_oalTimer。 2.1.6 pOEMUpdateRescheduleTime 内核中定义的函数指针,如果要采用Variable Tick Scheduler就有必要实现这个函数,主要是根据变化了的系统节拍(tick)重新设置时钟的定时周期,从而避免不必要的时钟中断。 PQOAL提供了OALTimerUpdateRescheduleTime函数的例子可以参考实现,它会调用与硬件相关的函数OALTimerUpdate()。 2.2 与其他OAL模块交互的变量和函数 2.2.1 OALTimerInit() 这个函数用来初始化系统时钟,通常是在OEMInit函数中调用。具体包括初始化OAL层变量g_oalTimer,初始化内核变量curridlehigh, curridlelow, idleconv,初始化内核函数指针(2.1.5,2.1.6),初始化硬件时钟定时器。 2.2.2 g_oalTimer,OAL_TIMER_STATE结构的变量,包含各种系统时钟相关的变量。 typedef struct { UINT32 countsPerMSec; // counts per 1 msec UINT32 countsMargin; // counts margin UINT32 maxPeriodMSec; // maximal timer period in MSec UINT32 msecPerSysTick; // msec per system tick UINT32 countsPerSysTick; // counts per system tick UINT32 actualMSecPerSysTick; // actual msec per system tick UINT32 actualCountsPerSysTick; // actual counts per system tick volatile UINT64 curCounts; // counts at last system tick } OAL_TIMER_STATE, *POAL_TIMER_STATE; 2.2.3 OALTimerCountsSinceSysTick() 这个函数使用高精度计数器返回从上一次系统嘀哒后经过的时间,一般需要精度高于1ms,换句话说,硬件时钟的晶振频率要高于1MHz。 2.2.4 OALTimerUpdateRescheduleTime(),参见2.1.6 2.2.5 OALTimerRecharge(),OALTimerUpdate(),OALStall() 2.2.6 OALGetTickCount() 3. 实现高精度定时器 有时用户程序希望调用QueryPerformanceCounter()和QueryPerformanceFrequency()实现精度高于1ms的计时。按照2.1.5所述,PQOAL已经基本实现了这部分代码,我们只需根据所采用的CPU实现OALTimerCountsSinceSysTick(2.2.3)。 PXA270处理器有两组时钟通道,允许通过软件设置时钟中断,他们被称作操作系统时钟(Operating System Timer),详细内容请参考《Intel PXA27x Processor Family Developer's Manual》。 这里只介绍一组与PXA255兼容的时钟通道,它采用3.25M晶振,即每毫秒可以计数3250次,有了它就可以实现精度高于1ms的计时器。 INT32 OALTimerCountsSinceSysTick() { UINT32 LastTimerMatch = (g_XllpOSTHandle.pOSTRegs->osmr0 - g_oalTimer.countsPerSysTick); // How many ticks since the last timer interrupt? return (g_XllpOSTHandle.pOSTRegs->oscr0 - LastTimerMatch); }