更多内容请访问:Harmonyos技术社区https://harmonyos.51cto.com,与华为共同打造官方,关于源码解析在鸿蒙LightKernel的一篇文章中,我们分析了中断的源码,简单提到了Tick中断。本文将继续分析Tick和时间相关的源码,向读者介绍鸿蒙LightKernel的时间管理模块。时间管理模块以系统时钟为基础,分为两部分,一部分是SysTick中断,为任务调度提供必要的时钟节拍;另一部分是为应用程序提供所有与时间相关的服务,例如时间转换和统计功能。系统时钟由定时器/计数器产生的输出脉冲触发的中断产生,一般定义为整数或长整数。输出脉冲的周期称为“时钟节拍”,也称为时标或节拍。Tick是操作系统的基本时间单位,由用户配置的每秒Ticks数决定。如果用户将每秒的Ticks数配置为1000,则1Tick等于1ms的持续时间。另一个计时单位是Cycle,它是系统的最小计时单位。一个周期的持续时间由主系统时钟的频率决定。系统主时钟的频率是每秒的周期数。对于216MHzCPU,1秒内生成216000000个周期。用户以秒和毫秒计数,而操作系统以滴答计数。当用户需要对系统进行任务暂停、延迟等操作时,可以使用时间管理模块来监控Tick和秒/毫秒。转变。接下来我们分析时间管理模块的源码。如果涉及开发板,以开发板项目targets\cortex-m7_nucleo_f767zi_gcc\为例分析源码。1.时间管理初始化和启动我们先来看一下时间管理模块的相关配置,然后分析如何初始化和启动。1.1时间管理相关配置时间管理模块涉及3个配置项,系统时钟OS_SYS_CLOCK、每秒Ticks数LOSCFG_BASE_CORE_TICK_PER_SECOND两个配置选项,以及宏LOSCFG_BASE_CORE_TICK_HW_TIME。LOSCFG_BASE_CORE_TICK_HW_TIME默认禁用。启用时,需要提供一个自定义函数VOIDplatform_tick_handler(VOID),用于在Tick中断处理函数中执行自定义操作。这些配置项在模板开发板工程目录的文件target_config.h中定义,如文件targets\cortex-m7_nucleo_f767zi_gcc\target_config.h中定义如下:#defineOS_SYS_CLOCK96000000#defineLOSCFG_BASE_CORE_TICK_PER_SECOND(1000UL)#defineLOSCFG_BASE_CORE_TICK_HW_TIME01.2时间管理初始化和启动函数INT32main(VOID)willcallthefunctionUINT32LOS_Start(VOID)inkernel\src\los_init.ctostartthesystem,whichwillcallthestartupschedulingfunctionUINT32HalStartSchedule(OS_TICK_HANDLERhandler).Thesourcecodeisasfollows:LITE_OS_SEC_TEXT_INITUINT32LOS_Start(VOID){returnHalStartSchedule(OsTickHandler);}ThefunctionUINT32HalTickStart(OS_TICK_HANDLER*handler)isdefinedinkernel\arch\arm\cortex-m7\gcc\los_context.c,thesourcecodeisasfollows.ThefunctionparameteristheTickinterrupthandlerfunctionOsTickHandler(),whichwillbeanalyzedlater.(1)ThecodecontinuestocallthefunctiontofurthercallthefunctionHalTickStart(handler)tosettheTickinterruptstart.(2)TheassemblyfunctionHalStartToRunwillbecalledtostartrunningthesystem,andthesubsequenttaskschedulingserieswillanalyzetheassemblyfunctionindetail.LITE_OS_SEC_TEXT_INITUINT32HalStartSchedule(OS_TICK_HANDLERhandler){UINT32ret;⑴ret=HalTickStart(handler);if(ret!=LOS_OK){returnret;}⑵HalStartToRun();returnLOS_OK;/*neverreturn*/}ThefunctionHalTickStart(handler)isdefinedinthefile\\\cortex-m7\gcc\los_timer.c,thesourcecodeisasfollows,weanalyzethecodeimplementationofthefunction.(1)验证时间管理模块配置项的有效性。当打开宏LOSCFG_USE_SYSTEM_DEFINED_INTERRUPT时,使用系统定义的中断。将执行(2)处的代码,调用文件kernel\arch\arm\cortex-m7\gcc\los_interrupt.c中定义的函数OsSetVector()设置中断向量。这个函数会在中断系列中详细分析。(3)设置全局变量g_sysClock为系统时钟,g_cyclesPerTick为每个tick对应的周期数,初始化g_ullTickCount为0,表示系统tick中断的次数。(4)调用targets\cortex-m7_nucleo_f767zi_gcc\Drivers\CMSIS\Include\core_cm7.h文件中定义的内联函数uint32_tSysTick_Config(uint32_tticks)初始化并启动系统定时器Systick和中断。WEAKUINT32HalTickStart(OS_TICK_HANDLER*handler){UINT32ret;⑴if((OS_SYS_CLOCK==0)||(LOSCFG_BASE_CORE_TICK_PER_SECOND==0)||(LOSCFG_BASE_CORE_TICK_PER_SECOND>OS_SYS_CLOCK)){returnLOS_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){returnLOS_ERRNO_TICK_PER_SEC_TOO_SMALL;}returnLOS_OK;}1.3Tick中断处理函数OsTickHandler()函数VOIDOsTickHandler(VOID)定义在文件kernel\src\los_tick.c中,是时间管理中执行频率最高的函数模块。只要Tick中断发生,就会调用此函数。我们来分析一下这个函数的源码。如果在(1)处启用了宏LOSCFG_BASE_CORE_TICK_HW_TIME,则会调用自定义的tick处理函数platform_tick_handler(),默认不启用。⑵中,全局变量g_ullTickCount会被更新。⑶中,如果启用宏LOSCFG_BASE_CORE_TIMESLICE,则会检查当前运行任务的时间片,函数OsTimesliceCheck()在后续任务模块中会详细分析。⑷会遍历排序后的任务链表,检查是否有超时的任务。(5)如果支持定时器功能,会检查定时器是否超时。源码如下:LITE_OS_SEC_TEXTVOIDOsTickHandler(VOID){#if(LOSCFG_BASE_CORE_TICK_HW_TIME==1)⑴platform_tick_handler();#endif⑵g_ullTickCount++;#if(LOSCFG_BASE_CORE_TIMESLICE==1)⑶OsTimesliceCheck();#endif⑷OsTaskScan();//tasktimeoutscan#if(LOSCFG_BASE_CORE_SWTMR==1)⑸(VOID)OsSwtmrScan();#endif}2.LiteOS内核时间管理常用操作时间管理提供以下功能,时间转换,时间统计等这些函数定义在文件kernel\src\los_tick.c中,我们分析下面是这些操作的源码实现。2.1时间转换操作2.1.1将毫秒转换为Tick函数UINT32LOS_MS2Tick(UINT32millisec)可以将输入参数毫秒转换为Tick。代码中的OS_SYS_MS_PER_SECOND,即1秒等于1000毫秒。时间转换也比较简单。知道一秒有多少个Ticks,除以OS_SYS_MS_PER_SECOND得到1毫秒有多少个Ticks,再乘以毫秒,计算出Ticks个数的结果值并返回。LITE_OS_SEC_TEXT_MINORUINT32LOS_MS2Tick(UINT32millisec){if(millisec==OS_NULL_INT){returnOS_NULL_INT;}return((UINT64)millisec*LOSCFG_BASE_CORE_TICK_PER_SECOND)/OS_SYS_MS_PER_SECOND;}2.1.2Tick转化为毫秒函数UINT32LOS_Tick2MS(UINT32tick)把输入参数Tick数目转换为毫秒。时间转换也比较简单。将ticks数除以每秒Ticks数LOSCFG_BASE_CORE_TICK_PER_SECOND计算秒数,然后换算成毫秒,计算结果值并返回。LITE_OS_SEC_TEXT_MINORUINT32LOS_Tick2MS(UINT32ticks){return((UINT64)ticks*OS_SYS_MS_PER_SECOND)/LOSCFG_BASE_CORE_TICK_PER_SECOND;}2.1.3将周期数转换为毫秒在介绍转换函数之前先看下CpuTick结构体,结构体成员比较简单,只有2表示一个UINT64类型数据的高低32位值。typedefstructtagCpuTick{UINT32cntHi;/*<64位值的高32位*/UINT32cntLo;/*<64位值的低32位*/}CpuTick;继续看转换函数OsCpuTick2MS(),可以将CpuTick类型的周期数转换为对应的毫秒数,输出毫秒数据的高低32位值。看具体代码,查看⑴处的参数是否为空指针,查看⑵处是否配置了系统时钟。(3)将CpuTick结构体表示的周期数转换成UINT64类型的数据。(4)在(DOUBLE)g_sysClock/OS_SYS_MS_PER_SECOND处进行数值计算得到每毫秒的周期数,然后除以tmpCpuTick得到周期数对应的毫秒数。⑦处,将DOUBLE类型转换为UINT64类型,然后执行⑩,将结果值的高64位和低64位分别赋值给*msLo和*msHi。LITE_OS_SEC_TEXT_INITUINT32OsCpuTick2MS(CpuTick*cpuTick,UINT32*msHi,UINT32*msLo){UINT64tmpCpuTick;DOUBLEtemp;⑴if((cpuTick==NULL)||(msHi==NULL)||(msLo==NULL)){returnLOS_ERRNO_SYS_L}PTR_(2)g_sysClock==0){returnLOS_ERRNO_SYS_CLOCK_INVALID;}⑶tmpCpuTick=((UINT64)cpuTick->cntHi<
