时钟机制
在 SylixOS 中使用硬件定时器作为系统 tick 时钟,它是系统跳动的心脏。tick 时钟为系统的多任务调度提供依据,其驱动框架在内核中已经写好,驱动开发人员只需实现 tick 初始化、清除 tick 中断即可。
tick 时钟的初始化函数如下所示,它位于 BSP 下的“bsp/SylixOS/bsp/bspLib.c”内。
#include <SylixOS.h>
VOID bspTickInit (VOID)
{
ULONG ulVector = 1;
__tickTimerInit(LW_TICK_HZ);
GuiFullCnt = __sysClkGet(MCK) / 16 / LW_TICK_HZ;
Gui64NSecPerCnt7 = ((1000 * 1000 * 1000 / LW_TICK_HZ) << 7) / GuiFullCnt; /* TICK_IN_THREAD > 0 */
/*
* 初始化硬件定时器, 频率为 LW_TICK_HZ, 类型为自动重装, 启动硬件定时器
* 并将硬件定时器的向量中断号赋给 ulVector 变量
*/
API_InterVectorConnect(ulVector, (PINT_SVR_ROUTINE)__tickTimerIsr, LW_NULL, "tick_timer");
API_InterVectorEnable(ulVector);
}
其中 LW_TICK_HZ 是系统的 tick 时钟频率,单位是 Hz,其数值需要根据具体的硬件性能来设置,频率越快系统的额外开销也就越大。通常情况下 tick 时钟频率设置为 100Hz 或者 1000Hz,对应的时间精度为 10ms 或者 1ms。
一般情况下,系统 tick 时钟的精度只有 10ms 或 1ms,不能满足一些对于时间精度要求较高的应用程序,因此,为了获取高精度时钟,系统内提供 bspTickHighResolution 函数,该函数通过读取硬件定时器的当前计数值来修正最近一次 tick 到当前的精确时间。bspTickHighResolution 函数的定义如下所示,它位于 SylixOS BSP 下的“bsp/SylixOS/bsp/bspLib.c”内,以 AT91SAM9X25 的定时器为例。
#include <SylixOS.h>
Static UINT32 GuiFullCnt;
Static UINT64 Gui64NSecPerCnt7; /* 提高 7bit 精度 */
VOID bspTickHighResolution (struct timespec *ptv)
{
REGISTER UINT32 uiCntCur; /* 20位精度定时器 */
uiCntCur = (UINT32)readl(REG_PIT_PIIR) & (0xFFFFF); /*检查是否有 TICK 中断请求 */
#define BIT_INT_PIT 0x1
if (readl(REG_PIT_SR) & BIT_INT_PIT) {
/*
* 这里由于 TICK 没有及时更新, 所以需要重新获取并且加上一个 TICK 的时间
*/
uiCntCur = (UINT32)readl(REG_PIT_PIIR) & (0xFFFFF);
if (uiCntCur != GuiFullCnt) {
uiCntCur += GuiFullCnt;
}
}
ptv->tv_nsec += (LONG)((Gui64NSecPerCnt7 * uiCntCur) >> 7);
if (ptv->tv_nsec >= 1000000000) {
ptv->tv_nsec -= 1000000000;
ptv->tv_sec++;
}
}
由上面代码可以看出修正后的时钟精度可以达到纳秒,完全满足使用。
tick 时钟的中断服务函数完成系统全局时钟的记录以及多任务调度等功能,其定义如下所示。
#include <SylixOS.h>
static irqreturn_t __tickTimerIsr (VOID)
{
INT uiPending;
uiPending = readl(REG_PIT_SR);
if (uiPending & 1) { /* tick 中断 */
API_KernelTicksContext(); /* 保存被时钟中断的线程控制块 */
API_KernelTicks(); /* 内核 TICKS 通知 */
API_TimerHTicks(); /* 高速 TIMER TICKS 通知 */
__timerIsr();
return (LW_IRQ_HANDLED); /* tick中断,中断结束 */
}
/*
* 不是tick中断,继续遍历中断服务函数
*/
return (LW_IRQ_NONE);
}
每次当时钟中断发生时,内核内部计数器的值就会加一。内核内部计数器的值在系统引导时被初始化为 0,因此,其值就是自上次系统引导以来的时钟滴答数。该计数器是一个 64 位的变量,变量名为 _K_i64KernelTime,定义在“libSylixOS/SylixOS/kernel/include/ k_globalvar.h”文件内。