LiteOS:剖析时间管理模块源代码
摘要:HuaweiLiteOS 的时间管理模块以系统时钟为基础,分为 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 获取。位操作模块源代码、开发文档如下:
内核时间管理源代码
时间管理模块源文件,包括头文件 kernel\include\los_tick.h、私有头文件[kernel\base\include\los_tick_pri.h(https://gitee.com/LiteOS/LiteOS/blob/master/kernel/base/include/los_tick_pri.h、C
源代码文件 kernel\base\los_tick.c。
开发指南时间管理模块文档
在线文档https://gitee.com/LiteOS/LiteOS/blob/feature/doc/HuaweiLiteOSKernelDeveloperGuide_zh.md#%E6%97%B6%E9%97%B4%E7%AE%A1%E7%90%86。
下面,我们剖析下时间管理模块的源代码,以LiteOS
开源工程支持的板子之一STM32F769IDiscovery
为例进行源码分析。
1、时间管理初始化和启动。
我们先看下时间管理模块的相关配置,然后再剖析如何初始化,如何启动。
1.1 时间管理相关的配置
时间管理模块依赖系统时钟OS_SYS_CLOCK
和每秒Tick
数目LOSCFG_BASE_CORE_TICK_PER_SECOND
两个配置选项。在系统启动时,targets\STM32F769IDISCOVERY\Src\main.c
的main()
函数调用targets\STM32F769IDISCOVERY\Src\platform_init.c
文件中的void HardwareInit(void)
进行硬件初始化,初始化时会调用void SystemClock_Config(void)
进行系统时钟的配置。完成系统时钟的配置后,SystemCoreClock
赋值为216000000Hz
。通过下面两个宏定义,OS_SYS_CLOCK
也表示系统时钟。
文件kernel\include\los_config.h
:
文件targets\STM32F769IDISCOVERY\include\hisoc\clock.h
:
另外一个配置项,每秒Tick
数目LOSCFG_BASE_CORE_TICK_PER_SECOND
,用户可以通过LiteOS
提供的组件配置工具menuconfig
进行设置,配置路径在Kernel → Basic Config → Task → Tick Value Per Second
,支持的开发板也提供了默认值。
1.2 时间管理初始化 OsTickInit()
在系统启动时,在kernel\init\los_init.c
中调用VOID OsRegister(VOID)
设置系统时钟、Tick
配置。⑴处全局变量g_tickPerSecond
赋值为LOSCFG_BASE_CORE_TICK_PER_SECOND
,也表示每秒配置多少个Tick
。⑵处的宏定义把OS_SYS_CLOCK
赋值给g_sysClock
,都表示系统时钟。后文的代码解析会涉及这些变量的使用。
在kernel\init\los_init.c
中会继续调用UINT32 OsTickInit(UINT32 systemClock, UINT32 tickPerSecond)
来初始化时间配置。该函数需要 2 个参数,分别是上文配置的系统时钟和每秒的tick
数。进一步调用HalClockInit()
函数。
HalClockInit()
函数定义在targets\bsp\hw\arm\timer\arm_cortex_m\systick.c
,使用LOS_HwiCreate()
为中断号M_INT_NUM
创建一个中断,每一个Tick
中断发生时,都会调用中断处理程序OsTickHandler()
,这个函数后文会分析。
1.3 时间管理模块启动 OsTickStart()
在系统开始调度之前,函数INT32 main(VOID)
会调用系统启动函数VOID OsStart(VOID)
,它会调用时间模块启动函数OsTickStart()
,进一步调用HalClockStart()
。我们分析下函数的代码实现。
⑴处全局变量g_cyclesPerTick
表示每Tick
对应的cycle
数目。⑵处函数定义在arch\arm\cortex_m\cmsis\core_cm7.h
文件中,初始化系统定时器Systick
并启动,Systick
相关的代码自行阅读。⑶处调用LOS_HwiEnable()
函数使能Tick
中断。
文件kernel\base\los_tick.c
:
文件targets\bsp\hw\arm\timer\arm_cortex_m\systick.c
:
1.4 Tick 中断处理函数 OsTickHandler()
这是时间管理模块中执行最频繁的函数VOID OsTickHandler(VOID)
,每当Tick
中断发生时就会调用该函数。⑴处会更新全局数组全局数组g_tickCount
每个核的tick
数据。⑵和tickless
特性相关,后续系列分析。⑶处会遍历任务的排序链表,检查是否有超时的任务。⑷处如果支持定时器特性,会检查定时器排序链表中的定时器是否超时。
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
,计算出结果值。
2.1.2 Tick 转化为毫秒
函数UINT32 LOS_Tick2MS(UINT32 tick)
把输入参数Tick
数目转换为毫秒数。时间转换也比较简单,tick
除以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
数。
2.2.2 获取自系统启动以来的 Tick 数
UINT64 LOS_TickCountGet(VOID)
函数计算自系统启动以来的Tick
数。需要注意,在关中断的情况下不进行计数,不能作为准确时间使用。全局数组UINT64 g_tickCount[LOSCFG_KERNEL_CORE_NUM]
记录每一个核的自系统启动以来的Tick
数,每次 Tick 中断发生时,在函数VOID OsTickHandler(VOID)
中会更新这个数组的数据。我们取第一个核的Tick
数作为返回结果。
2.2.3 获取自系统启动以来的 Cycle 数
VOID LOS_GetCpuCycle(UINT32 *highCnt, UINT32 *lowCnt)
函数获取自系统启动以来的Cycle
数。这个函数调用定义在文件targets\bsp\hw\arm\timer\arm_cortex_m\systick.c
中的HalClockGetCycles()
函数获取 64 位的无符号整数。返回结果按高低 32 位的无符号数值UINT32 *highCnt, UINT32 *lowCnt
分别返回。
我们继续看下函数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 数。
2.2.4 获取自系统启动以来的纳秒数
函数UINT64 LOS_CurrNanosec(VOID)
计算获取自系统启动以来的纳秒数。HalClockGetCycles()
获取自系统启动以来的Cycle
数,除以表示每秒多少cycle
的系统时钟g_sysClock
,可以计算出自系统启动以来的秒数,然后乘以秒和纳秒的换算关系OS_SYS_NS_PER_SECOND
,即可获取自系统启动以来的纳秒数。代码中出现 2 次除以OS_SYS_NS_PER_MS
,来减小中间值避免数值溢出。
2.3 延时管理
2.3.1 LOS_Udelay()微秒等待
以us
为单位的忙等,但可以被优先级更高的任务抢占。该函数VOID LOS_Udelay(UINT32 usecs)
进一步调用targets\bsp\hw\arm\timer\arm_cortex_m\systick.c
文件中定义的函数VOID HalDelayUs(UINT32 usecs)
。
继续分析下函数VOID HalDelayUs(UINT32 usecs)
。微秒转换为纳秒,计算当前的纳秒数值,然后while
循环,使用汇编指令空操作,等待超时。
2.3.2 LOS_Mdelay()毫秒等待
以ms
为单位的忙等,但可以被优先级更高的任务抢占。该函数把参数UINT32 msecs
毫秒转换为微妙,需要考虑数值溢出的问题。
小结
本文带领大家一起剖析了LiteOS
时间管理模块的源代码。时间管理模块为任务调度提供必要的时钟节拍,会向应用程序提供所有和时间有关的服务,如时间转换、统计、延迟功能。
感谢阅读,如有任何问题、建议,都可以留言给我们: https://gitee.com/LiteOS/LiteOS/issues 。为了更容易找到LiteOS
代码仓,建议访问 https://gitee.com/LiteOS/LiteOS ,关注Watch
、点赞Star
、并Fork
到自己账户下,如下图,谢谢。
版权声明: 本文为 InfoQ 作者【华为云开发者社区】的原创文章。
原文链接:【http://xie.infoq.cn/article/7f088a2b4839c1c8776b637ee】。文章转载请联系作者。
评论