关于harmonyos:踩准时钟节拍玩转时间转换鸿蒙轻内核时间管理有妙招

5次阅读

共计 7643 个字符,预计需要花费 20 分钟才能阅读完成。

摘要: 本文率领大家一起分析了鸿蒙轻内核的工夫治理模块的源代码。工夫治理模块为任务调度提供必要的时钟节奏,会向应用程序提供所有和工夫无关的服务,如工夫转换、统计、提早性能。

本文分享自华为云社区《鸿蒙轻内核 M 核源码剖析系列六 工夫治理》,原文作者:zhushy。

本文会持续剖析 Tick 和工夫相干的源码,给读者介绍鸿蒙轻内核的工夫治理模块。本文中所波及的源码,以 OpenHarmony LiteOS- M 内核为例,均能够在开源站点 https://gitee.com/openharmony… 获取。

工夫治理模块以零碎时钟为根底,能够分为 2 局部,一部分是 SysTick 中断,为任务调度提供必要的时钟节奏;另外一部分是,给应用程序提供所有和工夫无关的服务,如工夫转换、统计性能。

零碎时钟是由定时器 / 计数器产生的输入脉冲触发中断产生的,个别定义为整数或长整数。输入脉冲的周期叫做一个“时钟滴答”,也称为时标或者 Tick。Tick 是操作系统的根本工夫单位,由用户配置的每秒 Tick 数决定。如果用户配置每秒的 Tick 数目为 1000,则 1 个 Tick 等于 1ms 的时长。另外一个计时单位是 Cycle,这是零碎最小的计时单位。Cycle 的时长由零碎主时钟频率决定,零碎主时钟频率就是每秒钟的 Cycle 数,对于 216 MHz 的 CPU,1 秒产生 216000000 个 cycles。

用户以秒、毫秒为单位计时,而操作系统以 Tick 为单位计时,当用户须要对系统进行操作时,例如工作挂起、延时等,此时能够应用工夫治理模块对 Tick 和秒 / 毫秒进行转换。

上面,咱们分析下工夫治理模块的源代码,若波及开发板局部,以开发板工程 targets\cortex-m7_nucleo_f767zi_gcc\ 为例进行源码剖析。

1、工夫治理初始化和启动

咱们先看下工夫治理模块的相干配置,而后再分析如何初始化,如何启动。

1.1 工夫治理相干的配置

工夫治理模块波及 3 个配置项,零碎时钟 OS_SYS_CLOCK、每秒 Tick 数目 LOSCFG_BASE_CORE_TICK_PER_SECOND 两个配置选项,还有宏 LOSCFG_BASE_CORE_TICK_HW_TIME。LOSCFG_BASE_CORE_TICK_HW_TIME 默认敞开,开启时,须要提供定制函数 VOID platform_tick_handler(VOID),在 Tick 中断处理函数中执行定制操作。这些配置项在模板开发板工程目录的文件 target_config.h 中定义,如文件 targets\cortex-m7_nucleo_f767zi_gcc\target_config.h 中定义如下:


#define OS_SYS_CLOCK                                        96000000
#define LOSCFG_BASE_CORE_TICK_PER_SECOND                    (1000UL)
#define LOSCFG_BASE_CORE_TICK_HW_TIME                       0

1.2 工夫治理初始化和启动

函数 INT32 main(VOID) 会调用 kernel\src\los_init.c 中的函数 UINT32 LOS_Start(VOID) 启动零碎,该函数会调用启动调度函数 UINT32 HalStartSchedule(OS_TICK_HANDLER handler)。源码如下:

LITE_OS_SEC_TEXT_INIT UINT32 LOS_Start(VOID)
{return HalStartSchedule(OsTickHandler);
}

函数 UINT32 HalTickStart(OS_TICK_HANDLER *handler) 定义在 kernel\arch\arm\cortex-m7\gcc\los_context.c,源码如下。其中函数参数为 Tick 中断处理函数 OsTickHandler(),后文会剖析该 tick 中断处理函数。⑴处代码持续调用函数进一步调用函数 HalTickStart(handler) 来设置 Tick 中断启动。⑵处会调用汇编函数 HalStartToRun 开始运行零碎,后续任务调度系列再详细分析该汇编函数。

LITE_OS_SEC_TEXT_INIT UINT32 HalStartSchedule(OS_TICK_HANDLER handler)
{
    UINT32 ret;
⑴  ret = HalTickStart(handler);
    if (ret != LOS_OK) {return ret;}
⑵  HalStartToRun();
    return LOS_OK; /* never return */
}

函数 HalTickStart(handler) 定义在文件 kernel\arch\arm\cortex-m7\gcc\los_timer.c,源码如下,咱们剖析下函数的代码实现。⑴处校验下工夫治理模块的配置项的合法性。在开启宏 LOSCFG_USE_SYSTEM_DEFINED_INTERRUPT 时,会应用零碎定义的中断。会执行⑵处的代码,调用定义在文件 kernel\arch\arm\cortex-m7\gcc\los_interrupt.c 中的函数 OsSetVector() 设置中断向量,该函数在中断系列会详细分析。⑶处设置全局变量 g_sysClock 为零碎时钟,g_cyclesPerTick 为每 tick 对应的 cycle 数目,g_ullTickCount 初始化为 0,示意零碎 tick 中断产生次数。⑷处调用定义在 targets\cortex-m7_nucleo_f767zi_gcc\Drivers\CMSIS\Include\core_cm7.h 文件中的内联函数 uint32_t SysTick_Config(uint32_t ticks),初始化、启动零碎定时器 Systick 和中断。

WEAK UINT32 HalTickStart(OS_TICK_HANDLER *handler)
{
    UINT32 ret;

⑴  if ((OS_SYS_CLOCK == 0) ||
        (LOSCFG_BASE_CORE_TICK_PER_SECOND == 0) ||
        (LOSCFG_BASE_CORE_TICK_PER_SECOND > OS_SYS_CLOCK)) {return LOS_ERRNO_TICK_CFG_INVALID;}

#if (LOSCFG_USE_SYSTEM_DEFINED_INTERRUPT == 1)
#if (OS_HWI_WITH_ARG == 1)
    OsSetVector(SysTick_IRQn, (HWI_PROC_FUNC)handler, NULL);
#else
⑵  OsSetVector(SysTick_IRQn, (HWI_PROC_FUNC)handler);
#endif
#endif

⑶  g_sysClock = OS_SYS_CLOCK;
    g_cyclesPerTick = OS_SYS_CLOCK / LOSCFG_BASE_CORE_TICK_PER_SECOND;
    g_ullTickCount = 0;

⑷  ret = SysTick_Config(g_cyclesPerTick);
    if (ret == 1) {return LOS_ERRNO_TICK_PER_SEC_TOO_SMALL;}

    return LOS_OK;
}

1.3 Tick 中断处理函数 OsTickHandler()

文件 kernel\src\los_tick.c 定义的函数 VOID OsTickHandler(VOID),是工夫治理模块中执行最频繁的函数,每当 Tick 中断产生时就会调用该函数。咱们剖析下该函数的源码,⑴处如果开启宏 LOSCFG_BASE_CORE_TICK_HW_TIME,会调用定制的 tick 处理函数 platform_tick_handler(),默认不开启。⑵处会更新全局变量 g_ullTickCount,⑶处如果开启宏 LOSCFG_BASE_CORE_TIMESLICE,会查看以后运行工作的工夫片,在后续工作模块会详细分析下函数 OsTimesliceCheck()。⑷处会遍历工作的排序链表,查看是否有超时的工作。⑸处如果反对定时器个性,会查看定时器是否超时。

源码如下:

LITE_OS_SEC_TEXT VOID OsTickHandler(VOID)
{#if (LOSCFG_BASE_CORE_TICK_HW_TIME == 1)
⑴  platform_tick_handler();
#endif

⑵  g_ullTickCount++;

#if (LOSCFG_BASE_CORE_TIMESLICE == 1)
⑶  OsTimesliceCheck();
#endif

⑷   OsTaskScan();  // task timeout scan

#if (LOSCFG_BASE_CORE_SWTMR == 1)
⑸  (VOID)OsSwtmrScan();
#endif
}

2、LiteOS 内核工夫治理罕用操作

工夫治理提供上面几种性能,工夫转换、工夫统计等,这些函数定义在文件 kernel\src\los_tick.c,咱们分析下这些操作的源代码实现。

2.1 工夫转换操作

2.1.1 毫秒转换成 Tick

函数 UINT32 LOS_MS2Tick(UINT32 millisec) 把输出参数毫秒数 UINT32 millisec 能够转化为 Tick 数目。代码中 OS_SYS_MS_PER_SECOND,即 1 秒等于 1000 毫秒。工夫转换也比较简单,晓得一秒多少 Tick,除以 OS_SYS_MS_PER_SECOND,得出 1 毫秒多少 Tick,而后乘以 millisec,计算出 Tick 数目的后果值并返回。

LITE_OS_SEC_TEXT_MINOR UINT32 LOS_MS2Tick(UINT32 millisec)
{if (millisec == OS_NULL_INT) {return OS_NULL_INT;}

    return ((UINT64)millisec * LOSCFG_BASE_CORE_TICK_PER_SECOND) / OS_SYS_MS_PER_SECOND;
}

2.1.2 Tick 转化为毫秒

函数 UINT32 LOS_Tick2MS(UINT32 tick) 把输出参数 Tick 数目转换为毫秒数。工夫转换也比较简单,ticks 数目除以每秒多少 Tick 数值 LOSCFG_BASE_CORE_TICK_PER_SECOND,计算出多少秒,而后转换成毫秒,计算出后果值并返回。

LITE_OS_SEC_TEXT_MINOR UINT32 LOS_Tick2MS(UINT32 ticks)
{return ((UINT64)ticks * OS_SYS_MS_PER_SECOND) / LOSCFG_BASE_CORE_TICK_PER_SECOND;
}

2.1.3 Cycle 数目转化为毫秒

介绍转换函数之前,先看下一个 CpuTick 构造体,构造体比较简单,就 2 个成员,别离示意一个 UINT64 类型数据的高、低 32 位数值。

typedef struct tagCpuTick {
    UINT32 cntHi; /* < 一个 64 位数值的高 32 位 */
    UINT32 cntLo; /* < 一个 64 位数值的低 32 位 */
} CpuTick;

持续看转换函数 OsCpuTick2MS(),它能够把 CpuTick 类型示意的 cycle 数目转换为对应的毫秒数,输入毫秒数据的高、低 32 位数值。看下具体的代码,⑴处校验参数是否为空指针,⑵处查看零碎时钟是否配置。⑶处把 CpuTick 构造体示意的 cycle 数目转化为 UINT64 类型数据。⑷处进行数值计算,(DOUBLE)g_sysClock / OS_SYS_MS_PER_SECOND 失去每毫秒多少个 cycle 数,而后和 tmpCpuTick 做除法运算,失去 cycle 数目对应的毫秒数目。⑸处把 DOUBLE 类型转换为 UINT64 类型,而后执行⑹,别离把后果数值的高、低 64 位赋值给 msLo、msHi。

LITE_OS_SEC_TEXT_INIT UINT32 OsCpuTick2MS(CpuTick *cpuTick, UINT32 *msHi, UINT32 *msLo)
{
    UINT64 tmpCpuTick;
    DOUBLE temp;

⑴  if ((cpuTick == NULL) || (msHi == NULL) || (msLo == NULL)) {return LOS_ERRNO_SYS_PTR_NULL;}

⑵  if (g_sysClock == 0) {return LOS_ERRNO_SYS_CLOCK_INVALID;}
⑶  tmpCpuTick = ((UINT64)cpuTick->cntHi << OS_SYS_MV_32_BIT) | cpuTick->cntLo;
⑷  temp = tmpCpuTick / ((DOUBLE)g_sysClock / OS_SYS_MS_PER_SECOND);

    tmpCpuTick = (UINT64)temp;

    *msLo = (UINT32)tmpCpuTick;
    *msHi = (UINT32)(tmpCpuTick >> OS_SYS_MV_32_BIT);

    return LOS_OK;
}

2.1.4 Cycle 数目转化为微秒

转换函数 OsCpuTick2US(),它能够把 CpuTick 类型示意的 cycle 数目转换为对应的毫秒数,输入毫秒数据的高、低 32 位数值。该函数和 OsCpuTick2MS() 相似,自行浏览即可。

LITE_OS_SEC_TEXT_INIT UINT32 OsCpuTick2US(CpuTick *cpuTick, UINT32 *usHi, UINT32 *usLo)
{
    UINT64 tmpCpuTick;
    DOUBLE temp;

    if ((cpuTick == NULL) || (usHi == NULL) || (usLo == NULL)) {return LOS_ERRNO_SYS_PTR_NULL;}

    if (g_sysClock == 0) {return LOS_ERRNO_SYS_CLOCK_INVALID;}
    tmpCpuTick = ((UINT64)cpuTick->cntHi << OS_SYS_MV_32_BIT) | cpuTick->cntLo;
    temp = tmpCpuTick / ((DOUBLE)g_sysClock / OS_SYS_US_PER_SECOND);

    tmpCpuTick = (UINT64)temp;

    *usLo = (UINT32)tmpCpuTick;
    *usHi = (UINT32)(tmpCpuTick >> OS_SYS_MV_32_BIT);

    return LOS_OK;
}

2.2 工夫统计操作

2.2.1 获取每个 Tick 等于多少 Cycle 数

函数 UINT32 LOS_CyclePerTickGet(VOID) 计算 1 个 tick 等于多少 cycle。g_sysClock 零碎时钟示意 1 秒多少 cycle,LOSCFG_BASE_CORE_TICK_PER_SECOND 一秒多少 tick,相除计算出 1 tick 多少 cycle 数,即 g_cyclesPerTick = g_sysClock / LOSCFG_BASE_CORE_TICK_PER_SECOND。

LITE_OS_SEC_TEXT_MINOR UINT32 LOS_CyclePerTickGet(VOID)
{return g_cyclesPerTick;}

2.2.2 获取自系统启动以来的 Tick 数

UINT64 LOS_TickCountGet(VOID) 函数计算自系统启动以来的 Tick 中断的次数。须要留神,在关中断的状况下不进行计数,不能作为精确工夫应用。每次 Tick 中断产生时,在函数 VOID OsTickHandler(VOID) 中会更新 g_ullTickCount 数据。

LITE_OS_SEC_TEXT_MINOR UINT64 LOS_TickCountGet(VOID)
{return g_ullTickCount;}

2.2.3 获取零碎时钟

UINT32 LOS_SysClockGet(VOID) 函数获取配置的零碎时钟。

UINT32 LOS_SysClockGet(VOID)
{return g_sysClock;}

2.2.4 获取系统启动以来的 Cycle 数

函数 VOID HalGetCpuCycle(UINT32 cntHi, UINT32 cntLo) 定义在文件 kernel\arch\arm\cortex-m7\gcc\los_timer.c 中,该函数获取系统启动以来的 Cycle 数。返回后果按高、低 32 位的无符号数值 UINT32 cntHi, UINT32 cntLo 别离返回。

咱们看下该函数的源码。先关中断,而后⑴处获取启动启动以来的 Tick 数目。⑵处通过读取以后值寄存器 SysTick Current Value Register,获取 hwCycle。⑶处示意中断管制和状态寄存器 Interrupt Control and State Register 的第 TICK_CHECK 位为 1 时,示意挂起 systick 中断,tick 没有计数,须要加 1 校准。⑷处依据 swTick、g_cyclesPerTick 和 hwCycle 计算出自系统启动以来的 Cycle 数。⑸处获取 Cycle 数的高、低 32 位的无符号数值,而后开中断、返回。

LITE_OS_SEC_TEXT_MINOR VOID HalGetCpuCycle(UINT32 *cntHi, UINT32 *cntLo)
{
    UINT64 swTick;
    UINT64 cycle;
    UINT32 hwCycle;
    UINTPTR intSave;

    intSave = LOS_IntLock();

⑴  swTick = g_ullTickCount;
⑵  hwCycle = SysTick->VAL;

⑶  if ((SCB->ICSR & TICK_CHECK) != 0) {
        hwCycle = SysTick->VAL;
        swTick++;
    }

⑷  cycle = (((swTick) * g_cyclesPerTick) + (g_cyclesPerTick - hwCycle));

⑸  *cntHi = cycle >> SHIFT_32_BIT;
    *cntLo = cycle & CYCLE_CHECK;

    LOS_IntRestore(intSave);

    return;
}

小结

本文率领大家一起分析了鸿蒙轻内核的工夫治理模块的源代码。工夫治理模块为任务调度提供必要的时钟节奏,会向应用程序提供所有和工夫无关的服务,如工夫转换、统计、提早性能。后续也会陆续推出更多的分享文章,敬请期待,也欢送大家分享学习、应用鸿蒙轻内核的心得,有任何问题、倡议,都能够留言给咱们:https://gitee.com/openharmony…。为了更容易找到鸿蒙轻内核代码仓,倡议拜访 https://gitee.com/openharmony…,关注 Watch、点赞 Star、并 Fork 到本人账户下,谢谢。

点击关注,第一工夫理解华为云陈腐技术~

正文完
 0