鸿蒙轻内核M核源码分析系列五-时间管理

系统
文章由鸿蒙社区产出,想要了解更多内容请前往:51CTO和华为官方战略合作共建的鸿蒙技术社区https://harmonyos.51cto.com

[[398138]]

想了解更多内容,请访问:

51CTO和华为官方合作共建的鸿蒙技术社区

https://harmonyos.51cto.com

 在鸿蒙轻内核源码分析上一篇文章中,我们剖析了中断的源码,简单提到了Tick中断。本文会继续分析Tick和时间相关的源码,给读者介绍鸿蒙轻内核的时间管理模块。

时间管理模块以系统时钟为基础,可以分为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中定义如下:

  1. #define OS_SYS_CLOCK                                        96000000 
  2. #define LOSCFG_BASE_CORE_TICK_PER_SECOND                    (1000UL) 
  3. #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)。源码如下:

  1. LITE_OS_SEC_TEXT_INIT UINT32 LOS_Start(VOID) 
  2.     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开始运行系统,后续任务调度系列再详细分析该汇编函数。

  1. LITE_OS_SEC_TEXT_INIT UINT32 HalStartSchedule(OS_TICK_HANDLER handler) 
  2.     UINT32 ret; 
  3. ⑴  ret = HalTickStart(handler); 
  4.     if (ret != LOS_OK) { 
  5.         return ret; 
  6.     } 
  7. ⑵  HalStartToRun(); 
  8.     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和中断。

  1. WEAK UINT32 HalTickStart(OS_TICK_HANDLER *handler) 
  2.     UINT32 ret; 
  3.  
  4. ⑴  if ((OS_SYS_CLOCK == 0) || 
  5.         (LOSCFG_BASE_CORE_TICK_PER_SECOND == 0) || 
  6.         (LOSCFG_BASE_CORE_TICK_PER_SECOND > OS_SYS_CLOCK)) { 
  7.         return LOS_ERRNO_TICK_CFG_INVALID; 
  8.     } 
  9.  
  10. #if (LOSCFG_USE_SYSTEM_DEFINED_INTERRUPT == 1) 
  11. #if (OS_HWI_WITH_ARG == 1) 
  12.     OsSetVector(SysTick_IRQn, (HWI_PROC_FUNC)handler, NULL); 
  13. #else 
  14. ⑵  OsSetVector(SysTick_IRQn, (HWI_PROC_FUNC)handler); 
  15. #endif 
  16. #endif 
  17.  
  18. ⑶  g_sysClock = OS_SYS_CLOCK; 
  19.     g_cyclesPerTick = OS_SYS_CLOCK / LOSCFG_BASE_CORE_TICK_PER_SECOND; 
  20.     g_ullTickCount = 0; 
  21.  
  22. ⑷  ret = SysTick_Config(g_cyclesPerTick); 
  23.     if (ret == 1) { 
  24.         return LOS_ERRNO_TICK_PER_SEC_TOO_SMALL; 
  25.     } 
  26.  
  27.     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()。⑷处会遍历任务的排序链表,检查是否有超时的任务。⑸处如果支持定时器特性,会检查定时器是否超时。

源码如下:

  1. LITE_OS_SEC_TEXT VOID OsTickHandler(VOID) 
  2. #if (LOSCFG_BASE_CORE_TICK_HW_TIME == 1) 
  3. ⑴  platform_tick_handler(); 
  4. #endif 
  5.  
  6. ⑵  g_ullTickCount++; 
  7.  
  8. #if (LOSCFG_BASE_CORE_TIMESLICE == 1) 
  9. ⑶  OsTimesliceCheck(); 
  10. #endif 
  11.  
  12. ⑷   OsTaskScan();  // task timeout scan 
  13.  
  14. #if (LOSCFG_BASE_CORE_SWTMR == 1) 
  15. ⑸  (VOID)OsSwtmrScan(); 
  16. #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数目的结果值并返回。

  1. LITE_OS_SEC_TEXT_MINOR UINT32 LOS_MS2Tick(UINT32 millisec) 
  2.     if (millisec == OS_NULL_INT) { 
  3.         return OS_NULL_INT; 
  4.     } 
  5.  
  6.     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,计算出多少秒,然后转换成毫秒,计算出结果值并返回。

  1. LITE_OS_SEC_TEXT_MINOR UINT32 LOS_Tick2MS(UINT32 ticks) 
  2.     return ((UINT64)ticks * OS_SYS_MS_PER_SECOND) / LOSCFG_BASE_CORE_TICK_PER_SECOND; 

 2.1.3 Cycle数目转化为毫秒

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

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

 继续看转换函数OsCpuTick2MS(),它可以把CpuTick类型表示的cycle数目转换为对应的毫秒数,输出毫秒数据的高、低32位数值。看下具体的代码,⑴处校验参数是否为空指针,⑵处检查系统时钟是否配置。⑶处把CpuTick结构体表示的cycle数目转化为UINT64类型数据。⑷处进行数值计算,(DOUBLE)g_sysClock / OS_SYS_MS_PER_SECOND得到每毫秒多少个cycle数,然后和tmpCpuTick做除法运算,得到cycle数目对应的毫秒数目。⑸处把DOUBLE类型转换为UINT64类型,然后执行⑹,分别把结果数值的高、低64位赋值给*msLo、*msHi。

  1. LITE_OS_SEC_TEXT_INIT UINT32 OsCpuTick2MS(CpuTick *cpuTick, UINT32 *msHi, UINT32 *msLo) 
  2.     UINT64 tmpCpuTick; 
  3.     DOUBLE temp
  4.  
  5. ⑴  if ((cpuTick == NULL) || (msHi == NULL) || (msLo == NULL)) { 
  6.         return LOS_ERRNO_SYS_PTR_NULL; 
  7.     } 
  8.  
  9. ⑵  if (g_sysClock == 0) { 
  10.         return LOS_ERRNO_SYS_CLOCK_INVALID; 
  11.     } 
  12. ⑶  tmpCpuTick = ((UINT64)cpuTick->cntHi << OS_SYS_MV_32_BIT) | cpuTick->cntLo; 
  13. ⑷  temp = tmpCpuTick / ((DOUBLE)g_sysClock / OS_SYS_MS_PER_SECOND); 
  14.  
  15.     tmpCpuTick = (UINT64)temp
  16.  
  17.     *msLo = (UINT32)tmpCpuTick; 
  18.     *msHi = (UINT32)(tmpCpuTick >> OS_SYS_MV_32_BIT); 
  19.  
  20.     return LOS_OK; 

 2.1.4 Cycle数目转化为微秒

转换函数OsCpuTick2US(),它可以把CpuTick类型表示的cycle数目转换为对应的毫秒数,输出毫秒数据的高、低32位数值。该函数和OsCpuTick2MS()类似,自行阅读即可。

  1. LITE_OS_SEC_TEXT_INIT UINT32 OsCpuTick2US(CpuTick *cpuTick, UINT32 *usHi, UINT32 *usLo) 
  2.     UINT64 tmpCpuTick; 
  3.     DOUBLE temp
  4.  
  5.     if ((cpuTick == NULL) || (usHi == NULL) || (usLo == NULL)) { 
  6.         return LOS_ERRNO_SYS_PTR_NULL; 
  7.     } 
  8.  
  9.     if (g_sysClock == 0) { 
  10.         return LOS_ERRNO_SYS_CLOCK_INVALID; 
  11.     } 
  12.     tmpCpuTick = ((UINT64)cpuTick->cntHi << OS_SYS_MV_32_BIT) | cpuTick->cntLo; 
  13.     temp = tmpCpuTick / ((DOUBLE)g_sysClock / OS_SYS_US_PER_SECOND); 
  14.  
  15.     tmpCpuTick = (UINT64)temp
  16.  
  17.     *usLo = (UINT32)tmpCpuTick; 
  18.     *usHi = (UINT32)(tmpCpuTick >> OS_SYS_MV_32_BIT); 
  19.  
  20.     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。

  1. LITE_OS_SEC_TEXT_MINOR UINT32 LOS_CyclePerTickGet(VOID) 
  2.     return g_cyclesPerTick; 

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

UINT64 LOS_TickCountGet(VOID)函数计算自系统启动以来的Tick中断的次数。需要注意,在关中断的情况下不进行计数,不能作为准确时间使用。每次Tick中断发生时,在函数VOID OsTickHandler(VOID)中会更新g_ullTickCount数据。

  1. LITE_OS_SEC_TEXT_MINOR UINT64 LOS_TickCountGet(VOID) 
  2.     return g_ullTickCount; 

 2.2.3 获取系统时钟

UINT32 LOS_SysClockGet(VOID)函数获取配置的系统时钟。

  1. UINT32 LOS_SysClockGet(VOID) 
  2.     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位的无符号数值,然后开中断、返回。

  1. LITE_OS_SEC_TEXT_MINOR VOID HalGetCpuCycle(UINT32 *cntHi, UINT32 *cntLo) 
  2.     UINT64 swTick; 
  3.     UINT64 cycle; 
  4.     UINT32 hwCycle; 
  5.     UINTPTR intSave; 
  6.  
  7.     intSave = LOS_IntLock(); 
  8.  
  9. ⑴  swTick = g_ullTickCount; 
  10. ⑵  hwCycle = SysTick->VAL; 
  11.  
  12. ⑶  if ((SCB->ICSR & TICK_CHECK) != 0) { 
  13.         hwCycle = SysTick->VAL; 
  14.         swTick++; 
  15.     } 
  16.  
  17. ⑷  cycle = (((swTick) * g_cyclesPerTick) + (g_cyclesPerTick - hwCycle)); 
  18.  
  19. ⑸  *cntHi = cycle >> SHIFT_32_BIT; 
  20.     *cntLo = cycle & CYCLE_CHECK; 
  21.  
  22.     LOS_IntRestore(intSave); 
  23.  
  24.     return

 小结

本文带领大家一起剖析了鸿蒙轻内核的时间管理模块的源代码。时间管理模块为任务调度提供必要的时钟节拍,会向应用程序提供所有和时间有关的服务,如时间转换、统计、延迟功能。后续也会陆续推出更多的分享文章,敬请期待,为了更容易找到鸿蒙轻内核代码仓,建议访问 https://gitee.com/openharmony/kernel_liteos_m ,谢谢。

想了解更多内容,请访问:

51CTO和华为官方合作共建的鸿蒙技术社区

https://harmonyos.51cto.com

 

责任编辑:jianghua 来源: 鸿蒙社区
相关推荐

2021-06-04 09:57:49

鸿蒙HarmonyOS应用

2022-01-12 10:50:23

鸿蒙HarmonyOS应用

2022-01-10 15:31:44

鸿蒙HarmonyOS应用

2022-03-03 18:28:28

Harmony进程任务管理模块

2022-03-11 20:23:14

鸿蒙源码分析进程管理

2021-05-25 09:28:34

鸿蒙HarmonyOS应用

2021-10-20 16:08:57

鸿蒙HarmonyOS应用

2021-05-17 09:28:59

鸿蒙HarmonyOS应用

2021-06-04 14:15:10

鸿蒙HarmonyOS应用

2021-05-31 20:30:55

鸿蒙HarmonyOS应用

2022-04-13 11:02:12

鸿蒙事件模块事件Event

2021-06-09 09:48:01

鸿蒙HarmonyOS应用

2021-05-11 09:54:55

鸿蒙HarmonyOS应用

2021-05-21 09:25:11

鸿蒙HarmonyOS应用

2021-05-27 09:43:56

鸿蒙HarmonyOS应用

2021-09-22 14:36:32

鸿蒙HarmonyOS应用

2021-07-06 09:45:03

鸿蒙HarmonyOS应用

2022-03-31 16:26:49

鸿蒙源码分析进程管理

2021-05-12 09:45:20

鸿蒙HarmonyOS应用

2021-05-10 15:05:56

鸿蒙HarmonyOS应用
点赞
收藏

51CTO技术栈公众号