摘要: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 获取。位操作模块源代码、开发文档如下:

  • 内核时间管理源代码

时间管理模块源文件,包括头文件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.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:

/**
* @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
}

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数。这个函数调用定义在文件targets\bsp\hw\arm\timer\arm_cortex_m\systick.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)进一步调用targets\bsp\hw\arm\timer\arm_cortex_m\systick.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/LiteOS/issues 。为了更容易找到LiteOS代码仓,建议访问 https://gitee.com/LiteOS/LiteOS ,关注Watch、点赞Star、并Fork到自己账户下,如下图,谢谢。

点击关注,第一时间了解华为云新鲜技术~

LiteOS:剖析时间管理模块源代码的更多相关文章

  1. liteos时间管理(九)

    1. 时间管理 1.1 概述 1.1.1 概念 时间管理以系统时钟为基础.时间管理提供给应用程序所有和时间有关的服务. 系统时钟是由定时/计数器产生的输出脉冲触发中断而产生的,一般定义为整数或长整数. ...

  2. 蓝牙芯片NRF51822入门学习1:时间管理

    前言 之前辞职找工作的时候发现,很多公司希望招聘蓝牙技术方面的人才,所以干脆丢开LWIP静下心来学习蓝牙技术.原本以为一两星期能基本学会的,谁知道所选的蓝牙芯片nrf51822是个坑货,坑了我一个月. ...

  3. 解析Linux内核的基本的模块管理与时间管理操作---超时处理【转】

    转自:http://www.jb51.net/article/79960.htm 这篇文章主要介绍了Linux内核的基本的模块管理与时间管理操作,包括模块加载卸载函数的使用和定时器的用法等知识,需要的 ...

  4. nodejs 搭建自己的简易缓存cache管理模块

    http://www.infoq.com/cn/articles/built-cache-management-module-in-nodejs/ 为什么要搭建自己的缓存管理模块? 这个问题其实也是在 ...

  5. C# EasyUI树形结构权限管理模块

    最近悟出来一个道理,在这儿分享给大家:学历代表你的过去,能力代表你的现在,学习代表你的将来. 十年河东十年河西,莫欺少年穷 学无止境,精益求精 本节和大家探讨下C#使用EasyUI树形结构/Tree构 ...

  6. linux 时间管理——概念、注意点(一)【转】

    转自:http://www.cnblogs.com/openix/p/3324243.html 参考:1.http://bbs.eyeler.com/thread-69-1-1.html        ...

  7. Winform开发框架之字典管理模块的更新,附上最新2013年全国最新县及县以上行政区划代码sql脚本

    在很多项目里面,字典管理是必备的项目模块,而这个又是比较通用的功能,因此可以单独做成一个通用字典管理,例如这个模块,可以通过集成的方式,使用在我的<Winform开发框架>.<WCF ...

  8. phpcmsv9 幻灯片管理模块_UTF8

    幻灯片管理模块简介: .可创建多个位置,一个网站多个幻灯处调用互不影响. .独立模块,不修改系统内核,不用担心升级问题. .标签调用灵活. 安装: .复制本目录下面的phpcms目录到你的V9根目录下 ...

  9. linux设备驱动归纳总结(七):1.时间管理与内核延时【转】

    本文转载自:http://blog.chinaunix.net/uid-25014876-id-100005.html linux设备驱动归纳总结(七):1.时间管理与内核延时 xxxxxxxxxxx ...

  10. 如何使用 require.js ,实现js文件的异步加载,避免网页失去响应,管理模块之间的依赖性,便于代码的编写和维护。

    一.为什么要用require.js? 最早的时候,所有Javascript代码都写在一个文件里面,只要加载这一个文件就够了.后来,代码越来越多,一个文件不够了,必须分成多个文件,依次加载.下面的网页代 ...

随机推荐

  1. We Need More Bosses 题解

    We Need More Bosses 题目大意 给定一张图,找到两个点,使得这两个点之间的所有路径必须经过的边最多. 思路分析 我们先来思考一下如果已知两个点,怎么求两个点之间必须经过的边的数量. ...

  2. 虹科案例|Redis企业版数据库:金融行业客户案例解读

    传统银行无法提供无缝的全渠道客户体验.无法实时检测欺诈.无法获得业务洞察力.用户体验感较差.品牌声誉受损和业务损失?虹科提供的Redis企业版数据库具有低延迟.高吞吐和可用性性能,实施Redis企业版 ...

  3. 一些 trick 和思考收获

    2023.1.7 P1117 优秀的拆分 对于一眼看上去只能直接求解的题可以设置一些节点变为求每个节点的贡献 *2023 7.24 补充:这个 trick 也被称为设置关键点,通常用于区间长度固定或是 ...

  4. 3款免费又好用的 Docker 可视化管理工具

    前言 Docker提供了命令行工具(Docker CLI)来管理Docker容器.镜像.网络和数据卷等Docker组件.我们也可以使用可视化管理工具来更方便地查看和管理Docker容器.镜像.网络和数 ...

  5. RTMP协议学习——从握手到播放

    从客户端发起播放请求,到rtrmp视频流开始播放,大致经过了握手->建立连接->创建流->播放这几步比较重要的步骤.下面我将结合wireshark的抓包,对其中的每个流程进行分析和学 ...

  6. Vue 项目部署到GitHub Pages并同步到Gitee Pages

    前言:相信很多前端开发者都拥有自己的vue项目,若想把自己的项目做成网站分享给大家看,最常用的就是利用Github提供的GitHub Pages服务和Gitee提供的Gitee Pages服务.其中, ...

  7. 一套开源、强大且美观的WPF UI控件库 - HandyControl

    前言 今天给大家推荐一套开源.强大且美观的WPF UI控件库:HandyControl. WPF介绍 WPF 是一个强大的桌面应用程序框架,用于构建具有丰富用户界面的 Windows 应用.它提供了灵 ...

  8. vue常用方法封装收集

    // 格式化时间 export function formateTime(date) { var y = date.getFullYear(); var m = date.getMonth() + 1 ...

  9. 【Android】学习day05|简单登陆页面的实现|监听代码

    实现效果如下图所示 实现代码[部分] MainActivity.java 1 package com.example.app02; 2 3 import androidx.appcompat.app. ...

  10. .net 下优秀的DI框架推荐,看看你用过几个?

    在.NET生态系统中,有许多出色的依赖注入(DI)框架可供选择.每个框架都有其独特的特点和优点,可以根据项目需求和偏好进行选择.下面详细介绍一些.NET中优秀的DI框架,它们的优点以及适用场景. 1. ...