自己做网站外包,建立装修网站设计,认证网站所有权要添加代码,网站开发文档要求往期知识点记录#xff1a; 鸿蒙#xff08;HarmonyOS#xff09;应用层开发#xff08;北向#xff09;知识点汇总 持续更新中……
在鸿蒙轻内核源码分析上一篇文章中#xff0c;我们剖析了中断的源码#xff0c;简单提到了Tick中断。本文会继续分析Tick和时间相关的源…往期知识点记录 鸿蒙HarmonyOS应用层开发北向知识点汇总 持续更新中……
在鸿蒙轻内核源码分析上一篇文章中我们剖析了中断的源码简单提到了Tick中断。本文会继续分析Tick和时间相关的源码给读者介绍鸿蒙轻内核的时间管理模块。本文中所涉及的源码以OpenHarmony LiteOS-M内核为例均可以在开源站点https://gitee.com/openharmony/kernel_liteos_m 获取。
时间管理模块以系统时钟为基础可以分为2部分一部分是SysTick中断为任务调度提供必要的时钟节拍另外一部分是给应用程序提供所有和时间有关的服务如时间转换、统计功能。
系统时钟是由定时器/计数器产生的输出脉冲触发中断产生的一般定义为整数或长整数。输出脉冲的周期叫做一个“时钟滴答”也称为时标或者Tick。Tick是操作系统的基本时间单位由用户配置的每秒Tick数决定。如果用户配置每秒的Tick数目为1000则1个Tick等于1ms的时长。另外一个计时单位是Cycle这是系统最小的计时单位。Cycle的时长由系统主时钟频率决定系统主时钟频率就是每秒钟的Cycle数对于216 MHz的CPU1秒产生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 01.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秒多少cycleLOSCFG_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/kernel_liteos_m/issues 。为了更容易找到鸿蒙轻内核代码仓建议访问 https://gitee.com/openharmony/kernel_liteos_m关注Watch、点赞Star、并Fork到自己账户下谢谢。
写在最后
如果你觉得这篇内容对你还蛮有帮助我想邀请你帮我三个小忙
点赞转发有你们的 『点赞和评论』才是我创造的动力。关注小编同时可以期待后续文章ing不定期分享原创知识。想要获取更多完整鸿蒙最新学习资源请看下图提示