摘要:本文率领大家一起分析了鸿蒙轻内核的工夫治理模块的源代码。工夫治理模块为任务调度提供必要的时钟节奏,会向应用程序提供所有和工夫无关的服务,如工夫转换、统计、提早性能。
本文分享自华为云社区《鸿蒙轻内核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到本人账户下,谢谢。
点击关注,第一工夫理解华为云陈腐技术~