乐趣区

关于华为:LiteOS剖析时间管理模块源代码

摘要: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  15

VOID 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 到本人账户下,如下图,谢谢。

点击关注,第一工夫理解华为云陈腐技术~

退出移动版