从Var Tick角度来对CE电源管理
一.相关的基础知识如下
1.OAL中Timer相关函数说明
1> OALTimerInit
参数:
msecPerSysTick: 每个系统调度Tick对应多少ms;
countsPerMSec: Timer的Counter变化多少为1ms,其值为Timer的Base Clock/1000; margin:任何时钟源都有其偏差,该值指出偏差为多少,单位为Tick;
功能:
初始化系统Timer中断以及初始化系统时间的一些变量,如g_oalTimer/curridlehigh/ curridlelow,以及函数指针变量pQueryPerformanceFrequency和pQueryPerformanceCounter等。
2> OALTimerIntrHandler
参数:
无。
功能:
系统Timer中断Handle。
其返回值是系统SYSINTER,可能的值有SYSINTR_RESCHED和SYSINTR_NOP。如果是SYSINTR_RESCHED则说明当前的系统需要一次任务调度,如果是后者则不需要做任何处理。
3> OEMIdle
参数:
无。
功能:
当系统进入System Idle的时候会调用该函数,主要完成的功能是使CPU进入低功耗模式。
2.OAL中相关变量说明
CurMSec:
该全局变量表示在系统启动以后,过了多少毫秒。一般在OAL中有两个地方会用到该变量,一个是在系统Timer的ISR中断中要更新该变量,还有就是在OEMIdle中,当然这取决于你如何实现OEMIdle函数。用户调用GetTickCount函数的时候,实际上也是获得该变量的值。在MIPS架构的处理器上该变量名为AddrCurMSec。
dwReschedTime:
该变量指WinCE系统下一次调度的时间,它会和CurMSec配合使用,也是在系统Timer的ISR和OEMIdle中被用到。在系统Timer的ISR中,if((CurMSec - dwReschedTime) >= 0)则返回SYSINTR_RESCHED,否则返回SYSINTR_NOP。这个很好理解,如果当前时间大于系统下一次调度的时间就进行系统调度。在OEMIdle中,if ((dwReschedTime - CurMSec) > 0)则处理器进入Idle状态,否则立即返回。就是说当前距离下一次调度还有一段时间,则处理器可以进入Idle状态。
curridlelow,curridlehigh:
两个变量组成一个64bit的计数器,一个表示低32bit,一个表示高32bit。这两个遍两会在Timer初始化的时候被设置为0,会在OEMIdle函数中被更新,用于记录WinCE运行时的Idle时间,支持GetIdleTime函数。
dwDefaultThreadQuantum:
表示线程的时间片大小,默认值好像是100毫秒,可以在OEMInit函数中修改。
3.系统的工作模式
CE主要应用在嵌入式设备中,由于多未移动设备或者体积很小,因此对系统的待机时间和工作时间有较多的考虑。
大概从3.0开始,CE引入了电源管理。
与其它模块的通信方式如下:
标准的CE系统工作模式由下面5种:On/system idle/user idle/suspend/off。其中进入system idle的条件是当前系统中不存在Running的Thread,当进入该模式后,系统中的电源管理模块会去调用OEM_Idle,使系统进入低功耗模式。
其中idle模式的说明如下“When there are no threads ready to run, kernel will call OEMIdle to save power. For S3C2443X platform and our BSP, we let CPU go into “IDLE” state in OEMIdle. The clock to CPU core is stopped when in IDLE mode. When exceptions occur, CPU recovers and returns to normal mode before IRQHandler calls OEMInterruptHandler, OEMIdle just does busy loop. If OEMInterruptHandler is called, OEMIdle returns. In our platform, CPU is running in NORMAL mode unless in IDLE or SLEEP mode. Certain interrupt sources we configured in OEMPowerOff turn the CPU from SLEEP back into NORMAL again.”
二.VAR Tick介绍
1.Tick的概念
CE的任务调度的最小单位是Tick,一个Tick又可以对应一个或者多个MS,可以在OALTimerInit中通过配置g_oalTimer.msecPerSysTick来确定它们的对应关系。
2.Fixed Tick与VAR Tick的概念
这里都是引用Microsoft的概念,所以有必要解释一下。Fixed Tick和VAR Tick差别在于,一个Tick包含固定多个MS还是可变多个MS。对于Fixed Tick,Tick的长度在函数OALTimerInit中确定,对于VAR Tick,其长度在函数OALTimerInit和OEM_Idle中确定。
无论是Fixed Tick还是VAR Tick,系统在System Idle的时候都回去调用OEM_Idle进入低功耗模式。对于Fixed Tick情况,OEM_Idle函数会去配置系统在1 Tick之后唤醒。对于VAR Tick,OEM_Idle函数会计算需要多久才开始下一次系统调用,假设需要10 Tick才会开始下一次系统调度的话,则会配置Timer Controller,使其10 Tick单位后使系统唤醒。
这么说可能有点拗口,代码如下:
void OEMIdle(DWORD idleParam) { UINT32 baseMSec, idleMSec, idleSysTicks; INT32 usedCounts, idleCounts; ULARGE_INTEGER idle; // Get current system timer counter baseMSec = CurMSec;
// Compute the remaining idle time // 计算距离下次系统调度的时间,如果需要马上开始调度,则立即返回 idleMSec = dwReschedTime - baseMSec;
// Idle time has expired - we need to return if ((INT32)idleMSec <= 0) return;
// Limit the maximum idle time to what is supported. // Counter size is the limiting parameter. When kernel // profiler or interrupt latency timing is active it is set // to one system tick. // 是不是大于当前OAL中配置的Timer所支持的最大时间,如果大于它,则取最大值 // 其实这个最大值就是g_oalTimer.maxPeriodMSec,在OALTimerInit中配置,计算方法可以参照 // Timer的Spec,该值越大越好 if (idleMSec > g_oalTimer.maxPeriodMSec) { idleMSec = g_oalTimer.maxPeriodMSec; }
// We can wait only full systick idleSysTicks = idleMSec/g_oalTimer.msecPerSysTick;
// This is idle time in hi-res ticks idleCounts = idleSysTicks * g_oalTimer.countsPerSysTick;
// Find how many hi-res ticks was already used // 计算当前用了多少个Counter,即过了多少个Timer Base Clock的时钟周期 usedCounts = OALTimerCountsSinceSysTick();
if (usedCounts == g_oalTimer.countsPerSysTick) { return; }
// Prolong beat period to idle time -- don't do it idle time isn't // longer than one system tick. Even if OALTimerExtendSysTick function // should accept this value it can cause problems if kernel profiler // or interrupt latency timing is active. if (idleSysTicks > 1) { // Extend timer period // 根据计算出来的值重新配置Timer Counter的初始值,也即配置系统中断唤醒的时间。 // 如果计算出来的时间等于一个Tick的时间长,则不用进行配置 OALTimerUpdate(idleCounts-usedCounts, g_oalTimer.countsMargin); // Update value for timer interrupt which wakeup from idle // 更新系统Tick的时间长,以MS为单位,可以看到此处更新的是actualMSecPerSysTick的值 // 而msecPerSysTick的值并不变化 // 随之夜更新actualCountsPerSysTick的值 g_oalTimer.actualMSecPerSysTick = idleMSec; g_oalTimer.actualCountsPerSysTick = idleCounts; }
g_idleMSec = idleMSec;
// Move SoC/CPU to idle mode OALCPUIdle();
// Return system tick period back to original. Don't call when idle // time was one system tick. See comment above. if (idleSysTicks > 1) { // If there wasn't timer interrupt we have to update CurMSec&curCounts if (CurMSec == baseMSec) { // Return system tick period back to original // 这个需要特别注意,否则系统的CPU Loading会计算错误 idleCounts = OALTimerUpdate(g_oalTimer.countsPerSysTick, g_oalTimer.countsMargin)+usedCounts;
g_idleMSec = g_oalTimer.msecPerSysTick; // RETAILMSG(1,(TEXT("(%x)"), idleSysTicks));
// Restore original values g_oalTimer.actualMSecPerSysTick = g_oalTimer.msecPerSysTick; g_oalTimer.actualCountsPerSysTick = g_oalTimer.countsPerSysTick;
// Fix system tick counters & idle counter // 这个地方一定要注意,否则系统的调度会出现问题 CurMSec += idleCounts/g_oalTimer.countsPerMSec;
g_oalTimer.curCounts += idleCounts; idleCounts += OALTimerCountsSinceSysTick(); usedCounts = 0; } } else { if (CurMSec == baseMSec) { // Update actual idle counts, if there wasn't timer interrupt idleCounts = OALTimerCountsSinceSysTick(); usedCounts = 0; } }
// Get real idle value. If result is negative we didn't idle at all. idleCounts -= usedCounts;
if (idleCounts < 0) idleCounts = 0;
// RETAILMSG(1,(TEXT("(%x, %x)"), idleCounts, (CurMSec - baseMSec)*g_oalTimer.countsPerSysTick));
// Update idle counters // idle.QuadPart的值其实就是GetIdleTime的时间 idle.LowPart = curridlelow; idle.HighPart = curridlehigh; idle.QuadPart += idleCounts; curridlelow = idle.LowPart; curridlehigh = idle.HighPart; } |
3.VAR Tick的优点
省电。