LiteOS:剖析时间管理模块源代码
摘要:HuaweiLiteOS的时间管理模块以系统时钟为基础,分为2部分,一部分是SysTick中断,为任务调度提供必要的时钟节拍;另外一部分是,给应用程序提供所有和时间有关的服务,如时间转换、统计、延迟功能。
的时间管理模块以系统时钟为基础,可以分为2部分,一部分是中断,为任务调度提供必要的时钟节拍;另外一部分是,给应用程序提供所有和时间有关的服务,如时间转换、统计、延迟功能。
系统时钟是由定时器/计数器产生的输出脉冲触发中断产生的,一般定义为整数或长整数。输出脉冲的周期叫做一个“时钟滴答”,也称为时标或者。是操作系统的基本时间单位,由用户配置的每秒数决定。如果用户配置每秒的Tick数目为1000,则1个等于1ms的时长。另外一个计时单位是,这是系统最小的计时单位。的时长由系统主时钟频率决定,系统主时钟频率就是每秒钟的数,对于216 MHz的,1秒产生216000000个。
用户以秒、毫秒为单位计时,而操作系统以为单位计时,当用户需要对系统进行操作时,例如任务挂起、延时等,此时可以使用时间管理模块对和秒/毫秒进行转换。
文中所涉及的源代码,均可以在开源站点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、源代码文件kernel\base\los_tick.c。
开发指南时间管理模块文档
在线文档https://gitee.com/LiteOS/LiteOS/blob/feature/doc/Huawei
下面,我们剖析下时间管理模块的源代码,以开源工程支持的板子之一为例进行源码分析。
1、时间管理初始化和启动。
我们先看下时间管理模块的相关配置,然后再剖析如何初始化,如何启动。
1.1 时间管理相关的配置
时间管理模块依赖系统时钟和每秒数目两个配置选项。在系统启动时,的函数调用文件中的进行硬件初始化,初始化时会调用进行系统时钟的配置。完成系统时钟的配置后,赋值为。通过下面两个宏定义,也表示系统时钟。
文件:
/**
* @ingroup los_config
* System clock (unit: HZ)
*/
#ifndef OS_SYS_CLOCK
#define OS_SYS_CLOCK (get_bus_clk())
#endif
文件:
#define get_bus_clk() SystemCoreClock // default: 216000000
另外一个配置项,每秒数目,用户可以通过提供的组件配置工具进行设置,配置路径在,支持的开发板也提供了默认值。
1.2 时间管理初始化OsTickInit()
在系统启动时,在中调用设置系统时钟、配置。⑴处全局变量赋值为,也表示每秒配置多少个。⑵处的宏定义把赋值给,都表示系统时钟。后文的代码解析会涉及这些变量的使用。
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;
}
在中会继续调用来初始化时间配置。该函数需要2个参数,分别是上文配置的系统时钟和每秒的数。进一步调用函数。
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;
}
函数定义在,使用为中断号创建一个中断,每一个中断发生时,都会调用中断处理程序,这个函数后文会分析。
#define M_INT_NUM 15
VOID HalClockInit(VOID)
{
UINT32 ret = LOS_HwiCreate(M_INT_NUM, 0, 0, OsTickHandler, 0);
if (ret != 0) {
PRINTK("ret of LOS_HwiCreate = %#x\n", ret);
}
#if defined (LOSCFG_ARCH_ARM_CORTEX_M) && (LOSCFG_KERNEL_CPUP)
TimerHwiCreate();
#endif
}
1.3 时间管理模块启动OsTickStart()
在系统开始调度之前,函数会调用系统启动函数,它会调用时间模块启动函数,进一步调用。我们分析下函数的代码实现。
⑴处全局变量表示每对应的数目。⑵处函数定义在文件中,初始化系统定时器并启动,相关的代码自行阅读。⑶处调用函数使能中断。
文件:
LITE_OS_SEC_TEXT_INIT VOID OsTickStart(VOID)
{
HalClockStart();
}
文件:
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 = %#x\n", ret);
}
}
1.4 Tick中断处理函数OsTickHandler()
这是时间管理模块中执行最频繁的函数,每当中断发生时就会调用该函数。⑴处会更新全局数组全局数组每个核的数据。⑵和特性相关,后续系列分析。⑶处会遍历任务的排序链表,检查是否有超时的任务。⑷处如果支持定时器特性,会检查定时器排序链表中的定时器是否超时。
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、内核时间管理常用操作
的时间管理提供下面几种功能,时间转换、时间统计、延时管理等,我们剖析下这些接口的源代码实现。
2.1 时间转换操作
2.1.1 毫秒转换成Tick
函数把输入参数毫秒数可以转化为数目。代码中,即1秒等于1000毫秒。时间转换也比较简单,知道一秒多少,除以,得出1毫秒多少,然后乘以,计算出结果值。
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转化为毫秒
函数把输入参数数目转换为毫秒数。时间转换也比较简单,除以,计算出多少秒,然后转换成毫秒,计算出结果值。
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数
函数计算1个等于多少。系统时钟表示1秒多少,一秒多少,相除计算出多少数。
LITE_OS_SEC_TEXT_MINOR UINT32 LOS_CyclePerTickGet(VOID)
{
return g_sysClock / LOSCFG_BASE_CORE_TICK_PER_SECOND;
}
2.2.2 获取自系统启动以来的Tick数
函数计算自系统启动以来的数。需要注意,在关中断的情况下不进行计数,不能作为准确时间使用。全局数组记录每一个核的自系统启动以来的数,每次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数
函数获取自系统启动以来的数。这个函数调用定义在文件中的函数获取64位的无符号整数。返回结果按高低32位的无符号数值分别返回。
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);
}
我们继续看下函数函数。先关中断,然后⑴处获取启动启动以来的数目。⑵处通过读取当前值寄存器,获取。
⑷ cycle = (swTick * g_cyclesPerTick) + (g_cyclesPerTick - hwCycle);
⑶处表示中断控制和状态寄存器的第位为1时,表示挂起中断,没有计数,需要加1校准。⑷处根据、和计算出自系统启动以来的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 获取自系统启动以来的纳秒数
函数计算获取自系统启动以来的纳秒数。获取自系统启动以来的数,除以表示每秒多少的系统时钟,可以计算出自系统启动以来的秒数,然后乘以秒和纳秒的换算关系,即可获取自系统启动以来的纳秒数。代码中出现2次除以,来减小中间值避免数值溢出。
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()微秒等待
以为单位的忙等,但可以被优先级更高的任务抢占。该函数进一步调用文件中定义的函数。
LITE_OS_SEC_TEXT_MINOR VOID LOS_Udelay(UINT32 usecs)
{
HalDelayUs(usecs);
}
继续分析下函数。微秒转换为纳秒,计算当前的纳秒数值,然后循环,使用汇编指令空操作,等待超时。
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()毫秒等待
以为单位的忙等,但可以被优先级更高的任务抢占。该函数把参数毫秒转换为微妙,需要考虑数值溢出的问题。
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);
}
小结
本文带领大家一起剖析了时间管理模块的源代码。时间管理模块为任务调度提供必要的时钟节拍,会向应用程序提供所有和时间有关的服务,如时间转换、统计、延迟功能。
感谢阅读,如有任何问题、建议,都可以留言给我们: https://gitee.com/LiteOS/LiteOS/issues 。为了更容易找到代码仓,建议访问 https://gitee.com/LiteOS/LiteOS ,关注、点赞、并到自己账户下,如下图,谢谢。
