LiteOS内核源码分析系列四——时间管理(1)

Huawei LiteOS的时间管理模块以系统时钟为基础,可以分为2部分,一部分是SysTick中断,为任务调度提供必要的时钟节拍;另外一部分是,给应用程序提供所有和时间有关的服务,如时间转换、统计、延迟功能。

系统时钟是由定时器/计数器产生的输出脉冲触发中断产生的,一般定义为整数或长整数。输出脉冲的周期叫做一个“时钟滴答”,也称为时标或者TickTick是操作系统的基本时间单位,由用户配置的每秒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/Huawei_LiteOS_Kernel_Developer_Guide_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.cmain()函数调用targets\STM32F769IDISCOVERY\Src\platform_init.c文件中的void HardwareInit(void)进行硬件初始化,初始化时会调用void SystemClock_Config(void)进行系统时钟的配置。完成系统时钟的配置后,SystemCoreClock赋值为216000000Hz。通过下面两个宏定义,OS_SYS_CLOCK也表示系统时钟。

文件kernel\include\los_config.h:

/**
 * @ingroup los_config
 * System clock (unit: HZ)
 */
#ifndef OS_SYS_CLOCK
#define OS_SYS_CLOCK (get_bus_clk())
#endif

文件targets\STM32F769IDISCOVERY\include\hisoc\clock.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()

在系统启动时,在kernel\init\los_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;
}

kernel\init\los_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()函数定义在targets\bsp\hw\arm\timer\arm_cortex_m\systick.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 = %#x\n", 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数目。⑵处函数定义在arch\arm\cortex_m\cmsis\core_cm7.h文件中,初始化系统定时器Systick并启动,Systick相关的代码自行阅读。⑶处调用LOS_HwiEnable()函数使能Tick中断。

文件kernel\base\los_tick.c

LITE_OS_SEC_TEXT_INIT VOID OsTickStart(VOID)
{
    HalClockStart();
}

文件targets\bsp\hw\arm\timer\arm_cortex_m\systick.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 = %#x\n", 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
}
原文链接:,转发请注明来源!