摘要:Huawei LiteOS的工夫治理模块以零碎时钟为根底,分为2局部,一部分是SysTick中断,为任务调度提供必要的时钟节奏;另外一部分是,给应用程序提供所有和工夫无关的服务,如工夫转换、统计、提早性能。
本文分享自华为云社区《LiteOS内核源码剖析系列四 LiteOS内核源码剖析--工夫治理》,原文作者:zhushy 。
Huawei LiteOS的工夫治理模块以零碎时钟为根底,能够分为2局部,一部分是SysTick中断,为任务调度提供必要的时钟节奏;另外一部分是,给应用程序提供所有和工夫无关的服务,如工夫转换、统计、提早性能。
零碎时钟是由定时器/计数器产生的输入脉冲触发中断产生的,个别定义为整数或长整数。输入脉冲的周期叫做一个“时钟滴答”,也称为时标或者Tick。Tick是操作系统的根本工夫单位,由用户配置的每秒Tick数决定。如果用户配置每秒的Tick数目为1000,则1个Tick等于1ms的时长。另外一个计时单位是Cycle,这是零碎最小的计时单位。Cycle的时长由零碎主时钟频率决定,零碎主时钟频率就是每秒钟的Cycle数,对于216 MHz的CPU,1秒产生216000000个cycles。
用户以秒、毫秒为单位计时,而操作系统以Tick为单位计时,当用户须要对系统进行操作时,例如工作挂起、延时等,此时能够应用工夫治理模块对Tick和秒/毫秒进行转换。
文中所波及的源代码,均能够在LiteOS
开源站点https://gitee.com/LiteOS/LiteOS 获取。位操作模块源代码、开发文档如下:
- 内核工夫治理源代码
工夫治理模块源文件,包含头文件kernelincludelos_tick.h、公有头文件[kernelbaseincludelos_tick_pri.h](https://gitee.com/LiteOS/Lite...、C
源代码文件kernelbaselos_tick.c。
- 开发指南工夫治理模块文档
在线文档https://gitee.com/LiteOS/Lite...。
上面,咱们分析下工夫治理模块的源代码,以LiteOS开源工程反对的板子之一STM32F769IDiscovery为例进行源码剖析。
1、工夫治理初始化和启动。
咱们先看下工夫治理模块的相干配置,而后再分析如何初始化,如何启动。
1.1 工夫治理相干的配置
工夫治理模块依赖零碎时钟OS_SYS_CLOCK和每秒Tick数目LOSCFG_BASE_CORE_TICK_PER_SECOND两个配置选项。在系统启动时,targetsSTM32F769IDISCOVERYSrcmain.c的main()函数调用targetsSTM32F769IDISCOVERYSrcplatform_init.c文件中的void HardwareInit(void)进行硬件初始化,初始化时会调用void SystemClock_Config(void)进行零碎时钟的配置。实现零碎时钟的配置后,SystemCoreClock赋值为216000000Hz。通过上面两个宏定义,OS_SYS_CLOCK也示意零碎时钟。
文件kernelincludelos_config.h:
/** * @ingroup los_config * System clock (unit: HZ) */#ifndef OS_SYS_CLOCK#define OS_SYS_CLOCK (get_bus_clk())#endif
文件targetsSTM32F769IDISCOVERYincludehisocclock.h:
#define get_bus_clk() SystemCoreClock // default: 216000000
另外一个配置项,每秒Tick数目LOSCFG_BASE_CORE_TICK_PER_SECOND,用户能够通过LiteOS提供的组件配置工具menuconfig进行设置,配置门路在Kernel → Basic Config → Task → Tick Value Per Second,反对的开发板也提供了默认值。
1.2 工夫治理初始化OsTickInit()
在系统启动时,在kernelinitlos_init.c中调用VOID OsRegister(VOID)设置零碎时钟、Tick配置。⑴处全局变量g_tickPerSecond赋值为LOSCFG_BASE_CORE_TICK_PER_SECOND,也示意每秒配置多少个Tick。⑵处的宏定义把OS_SYS_CLOCK赋值给g_sysClock,都示意零碎时钟。后文的代码解析会波及这些变量的应用。
LITE_OS_SEC_TEXT_INIT static VOID OsRegister(VOID){#ifdef LOSCFG_LIB_CONFIGURABLE g_osSysClock = OS_SYS_CLOCK_CONFIG; g_tickPerSecond = LOSCFG_BASE_CORE_TICK_PER_SECOND_CONFIG; g_taskLimit = LOSCFG_BASE_CORE_TSK_LIMIT_CONFIG; g_taskMaxNum = g_taskLimit + 1; g_taskMinStkSize = LOSCFG_BASE_CORE_TSK_MIN_STACK_SIZE_CONFIG; g_taskIdleStkSize = LOSCFG_BASE_CORE_TSK_IDLE_STACK_SIZE_CONFIG; g_taskDfltStkSize = LOSCFG_BASE_CORE_TSK_DEFAULT_STACK_SIZE_CONFIG; g_taskSwtmrStkSize = LOSCFG_BASE_CORE_TSK_SWTMR_STACK_SIZE_CONFIG; g_swtmrLimit = LOSCFG_BASE_CORE_SWTMR_LIMIT_CONFIG; g_semLimit = LOSCFG_BASE_IPC_SEM_LIMIT_CONFIG; g_muxLimit = LOSCFG_BASE_IPC_MUX_LIMIT_CONFIG; g_queueLimit = LOSCFG_BASE_IPC_QUEUE_LIMIT_CONFIG; g_timeSliceTimeOut = LOSCFG_BASE_CORE_TIMESLICE_TIMEOUT_CONFIG;#else⑴ g_tickPerSecond = LOSCFG_BASE_CORE_TICK_PER_SECOND;#endif⑵ SET_SYS_CLOCK(OS_SYS_CLOCK);#ifdef LOSCFG_KERNEL_NX LOS_SET_NX_CFG(true);#else LOS_SET_NX_CFG(false);#endif LOS_SET_DL_NX_HEAP_BASE(LOS_DL_HEAP_BASE); LOS_SET_DL_NX_HEAP_SIZE(LOS_DL_HEAP_SIZE); return;}
在kernelinitlos_init.c中会持续调用UINT32 OsTickInit(UINT32 systemClock, UINT32 tickPerSecond)来初始化工夫配置。该函数须要2个参数,别离是上文配置的零碎时钟和每秒的tick数。进一步调用HalClockInit()函数。
LITE_OS_SEC_TEXT_INIT UINT32 OsTickInit(UINT32 systemClock, UINT32 tickPerSecond){ if ((systemClock == 0) || (tickPerSecond == 0) || (tickPerSecond > systemClock)) { return LOS_ERRNO_TICK_CFG_INVALID; } HalClockInit(); return LOS_OK;}
HalClockInit()函数定义在targetsbsphwarmtimerarm_cortex_msystick.c,应用LOS_HwiCreate()为中断号M_INT_NUM创立一个中断,每一个Tick中断产生时,都会调用中断处理程序OsTickHandler(),这个函数后文会剖析。
#define M_INT_NUM 15VOID HalClockInit(VOID){ UINT32 ret = LOS_HwiCreate(M_INT_NUM, 0, 0, OsTickHandler, 0); if (ret != 0) { PRINTK("ret of LOS_HwiCreate = %#xn", ret); }#if defined (LOSCFG_ARCH_ARM_CORTEX_M) && (LOSCFG_KERNEL_CPUP) TimerHwiCreate();#endif}
1.3 工夫治理模块启动OsTickStart()
在零碎开始调度之前,函数INT32 main(VOID)会调用系统启动函数VOID OsStart(VOID),它会调用工夫模块启动函数OsTickStart(),进一步调用HalClockStart()。咱们剖析下函数的代码实现。
⑴处全局变量g_cyclesPerTick示意每Tick对应的cycle数目。⑵处函数定义在archarmcortex_mcmsiscore_cm7.h文件中,初始化零碎定时器Systick并启动,Systick相干的代码自行浏览。⑶处调用LOS_HwiEnable()函数使能Tick中断。
文件kernelbaselos_tick.c:
LITE_OS_SEC_TEXT_INIT VOID OsTickStart(VOID){ HalClockStart();}
文件targetsbsphwarmtimerarm_cortex_msystick.c:
VOID HalClockStart(VOID){ if ((OS_SYS_CLOCK == 0) || (LOSCFG_BASE_CORE_TICK_PER_SECOND == 0) || (LOSCFG_BASE_CORE_TICK_PER_SECOND > OS_SYS_CLOCK)) { return; }⑴ g_cyclesPerTick = OS_CYCLE_PER_TICK;⑵ (VOID)SysTick_Config(OS_CYCLE_PER_TICK);⑶ UINT32 ret = LOS_HwiEnable(M_INT_NUM); if (ret != 0) { PRINTK("LOS_HwiEnable failed. ret = %#xn", ret); }}
1.4 Tick中断处理函数OsTickHandler()
这是工夫治理模块中执行最频繁的函数VOID OsTickHandler(VOID),每当Tick中断产生时就会调用该函数。⑴处会更新全局数组全局数组g_tickCount每个核的tick数据。⑵和tickless个性相干,后续系列剖析。⑶处会遍历工作的排序链表,查看是否有超时的工作。⑷处如果反对定时器个性,会查看定时器排序链表中的定时器是否超时。
LITE_OS_SEC_TEXT VOID OsTickHandler(VOID){ UINT32 intSave; TICK_LOCK(intSave);⑴ g_tickCount[ArchCurrCpuid()]++; TICK_UNLOCK(intSave);#ifdef LOSCFG_KERNEL_TICKLESS⑵ OsTickIrqFlagSet(OsTicklessFlagGet());#endif#if (LOSCFG_BASE_CORE_TICK_HW_TIME == YES) HalClockIrqClear(); /* diff from every platform */#endif#ifdef LOSCFG_BASE_CORE_TIMESLICE OsTimesliceCheck();#endif⑶ OsTaskScan(); /* task timeout scan */#if (LOSCFG_BASE_CORE_SWTMR == YES)⑷ OsSwtmrScan();#endif}
2、LiteOS内核工夫治理罕用操作
Huawei LiteOS的工夫治理提供上面几种性能,工夫转换、工夫统计、延时治理等,咱们分析下这些接口的源代码实现。
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,计算出后果值。
LITE_OS_SEC_TEXT_MINOR UINT32 LOS_MS2Tick(UINT32 millisec){ if (millisec == UINT32_MAX) { return UINT32_MAX; } return (UINT32)(((UINT64)millisec * LOSCFG_BASE_CORE_TICK_PER_SECOND) / OS_SYS_MS_PER_SECOND);}
2.1.2 Tick转化为毫秒
函数UINT32 LOS_Tick2MS(UINT32 tick)把输出参数Tick数目转换为毫秒数。工夫转换也比较简单,tick除以LOSCFG_BASE_CORE_TICK_PER_SECOND,计算出多少秒,而后转换成毫秒,计算出后果值。
LITE_OS_SEC_TEXT_MINOR UINT32 LOS_Tick2MS(UINT32 tick){ return (UINT32)(((UINT64)tick * OS_SYS_MS_PER_SECOND) / LOSCFG_BASE_CORE_TICK_PER_SECOND);}
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数。
LITE_OS_SEC_TEXT_MINOR UINT32 LOS_CyclePerTickGet(VOID){ return g_sysClock / LOSCFG_BASE_CORE_TICK_PER_SECOND;}
2.2.2 获取自系统启动以来的Tick数
UINT64 LOS_TickCountGet(VOID)函数计算自系统启动以来的Tick数。须要留神,在关中断的状况下不进行计数,不能作为精确工夫应用。全局数组UINT64 g_tickCount[LOSCFG_KERNEL_CORE_NUM]记录每一个核的自系统启动以来的Tick数,每次Tick中断产生时,在函数VOID OsTickHandler(VOID)中会更新这个数组的数据。咱们取第一个核的Tick数作为返回后果。
LITE_OS_SEC_TEXT_MINOR UINT64 LOS_TickCountGet(VOID){ UINT32 intSave; UINT64 tick; TICK_LOCK(intSave); tick = g_tickCount[0]; TICK_UNLOCK(intSave); return tick;}
2.2.3 获取自系统启动以来的Cycle数
VOID LOS_GetCpuCycle(UINT32 highCnt, UINT32 lowCnt)函数获取自系统启动以来的Cycle数。这个函数调用定义在文件targetsbsphwarmtimerarm_cortex_msystick.c中的HalClockGetCycles()函数获取64位的无符号整数。返回后果按高下32位的无符号数值UINT32 highCnt, UINT32 lowCnt别离返回。
LITE_OS_SEC_TEXT_MINOR VOID LOS_GetCpuCycle(UINT32 *highCnt, UINT32 *lowCnt){ UINT64 cycle; if ((highCnt == NULL) || (lowCnt == NULL)) { return; } cycle = HalClockGetCycles(); /* get the high 32 bits */ *highCnt = (UINT32)(cycle >> 32); /* get the low 32 bits */ *lowCnt = (UINT32)(cycle & 0xFFFFFFFFULL);}
咱们持续看下函数HalClockGetCycles()函数。先关中断,而后⑴处获取启动启动以来的Tick数目。⑵处通过读取以后值寄存器SysTick Current Value Register,获取hwCycle。
⑷ cycle = (swTick * g_cyclesPerTick) + (g_cyclesPerTick - hwCycle);
⑶处示意中断管制和状态寄存器Interrupt Control and State Register的第TICK_INTR_CHECK位为1时,示意挂起systick中断,tick没有计数,须要加1校准。⑷处依据swTick、g_cyclesPerTick和hwCycle计算出自系统启动以来的Cycle数。
UINT64 HalClockGetCycles(VOID){ UINT64 swTick; UINT64 cycle; UINT32 hwCycle; UINT32 intSave; intSave = LOS_IntLock();⑴ swTick = LOS_TickCountGet();⑵ hwCycle = SysTick->VAL;⑶ if ((SCB->ICSR & TICK_INTR_CHECK) != 0) { hwCycle = SysTick->VAL; swTick++; }⑷ cycle = (swTick * g_cyclesPerTick) + (g_cyclesPerTick - hwCycle); LOS_IntRestore(intSave);#if defined (LOSCFG_ARCH_ARM_CORTEX_M) && (LOSCFG_KERNEL_CPUP) cycle = HalClockGetCpupCycles() * TIMER_CYCLE_SWITCH;#endif return cycle;}
2.2.4 获取自系统启动以来的纳秒数
函数UINT64 LOS_CurrNanosec(VOID)计算获取自系统启动以来的纳秒数。HalClockGetCycles()获取自系统启动以来的Cycle数,除以示意每秒多少cycle的零碎时钟g_sysClock,能够计算出自系统启动以来的秒数,而后乘以秒和纳秒的换算关系OS_SYS_NS_PER_SECOND,即可获取自系统启动以来的纳秒数。代码中呈现2次除以OS_SYS_NS_PER_MS,来减小两头值防止数值溢出。
LITE_OS_SEC_TEXT_MINOR UINT64 LOS_CurrNanosec(VOID){ UINT64 nanos; nanos = HalClockGetCycles() * (OS_SYS_NS_PER_SECOND / OS_SYS_NS_PER_MS) / (g_sysClock / OS_SYS_NS_PER_MS); return nanos;}
2.3 延时治理
2.3.1 LOS_Udelay()微秒期待
以us为单位的忙等,但能够被优先级更高的工作抢占。该函数VOID LOS_Udelay(UINT32 usecs)进一步调用targetsbsphwarmtimerarm_cortex_msystick.c文件中定义的函数VOID HalDelayUs(UINT32 usecs)。
LITE_OS_SEC_TEXT_MINOR VOID LOS_Udelay(UINT32 usecs){ HalDelayUs(usecs);}
持续剖析下函数VOID HalDelayUs(UINT32 usecs)。微秒转换为纳秒,计算以后的纳秒数值,而后while循环,应用汇编指令空操作,期待超时。
VOID HalDelayUs(UINT32 usecs){ UINT64 tmo = LOS_CurrNanosec() + usecs * OS_SYS_NS_PER_US; while (LOS_CurrNanosec() < tmo) { __asm__ volatile ("nop"); }}
2.3.2 LOS_Mdelay()毫秒期待
以ms为单位的忙等,但能够被优先级更高的工作抢占。该函数把参数UINT32 msecs毫秒转换为奥妙,须要思考数值溢出的问题。
LITE_OS_SEC_TEXT_MINOR VOID LOS_Mdelay(UINT32 msecs){ UINT32 delayUs = (UINT32_MAX / OS_SYS_US_PER_MS) * OS_SYS_US_PER_MS; while (msecs > UINT32_MAX / OS_SYS_US_PER_MS) { HalDelayUs(delayUs); msecs -= (UINT32_MAX / OS_SYS_US_PER_MS); } HalDelayUs(msecs * OS_SYS_US_PER_MS);}
小结
本文率领大家一起分析了LiteOS工夫治理模块的源代码。工夫治理模块为任务调度提供必要的时钟节奏,会向应用程序提供所有和工夫无关的服务,如工夫转换、统计、提早性能。
感激浏览,如有任何问题、倡议,都能够留言给咱们: https://gitee.com/LiteOS/Lite... 。为了更容易找到LiteOS
代码仓,倡议拜访 https://gitee.com/LiteOS/LiteOS ,关注Watch
、点赞Star
、并Fork
到本人账户下,如下图,谢谢。
点击关注,第一工夫理解华为云陈腐技术~