OpenHarmony设备开发 轻量系统内核(LiteOS-M)基础内核时间管理
时间管理
基本概念
时间管理以系统时钟为基础,给应用程序提供所有和时间有关的服务。
系统时钟是由定时器/计数器产生的输出脉冲触发中断产生的,一般定义为整数或长整数。输出脉冲的周期叫做一个“时钟滴答”。系统时钟也称为时标或者Tick。
用户以秒、毫秒为单位计时,而操作系统以Tick为单位计时,当用户需要对系统进行操作时,例如任务挂起、延时等,此时需要时间管理模块对Tick和秒/毫秒进行转换。
OpenHarmony LiteOS-M内核时间管理模块提供时间转换、统计功能。
时间单位
- Cycle 系统最小的计时单位。Cycle的时长由系统主时钟频率决定,系统主时钟频率就是每秒钟的Cycle数。
- Tick Tick是操作系统的基本时间单位,由用户配置的每秒Tick数决定。
接口说明
OpenHarmony LiteOS-M内核的时间管理提供下面几种功能,接口详细信息可以查看API参考。
表1 时间转换
接口名 | 描述 |
LOS_MS2Tick | 毫秒转换成Tick。 |
LOS_Tick2MS | Tick转化为毫秒。 |
OsCpuTick2MS | Cycle数目转化为毫秒,使用2个UINT32类型的数值分别表示结果数值的高、低32位。 |
OsCpuTick2US | Cycle数目转化为微秒,使用2个UINT32类型的数值分别表示结果数值的高、低32位。 |
表2 时间统计
接口名 | 描述 |
LOS_SysClockGet | 获取系统时钟。 |
LOS_TickCountGet | 获取自系统启动以来的Tick数。 |
LOS_CyclePerTickGet | 获取每个Tick多少Cycle数。 |
LOS_CurrNanosec | 获取当前的时间,单位纳秒。 |
表3 时间注册
接口名 | 描述 |
LOS_TickTimerRegister | 重新注册系统时钟的定时器和对应的中断处理函数。 |
表4 延时
接口名 | 描述 |
LOS_MDelay | 延时函数,延时单位毫秒。 |
LOS_UDelay | 延时函数,延时单位微秒。 |
开发流程
时间管理的典型开发流程:
- 根据实际需求,完成板级配置适配,并配置系统主时钟频率OS_SYS_CLOCK(单位Hz)和LOSCFG_BASE_CORE_TICK_PER_SECOND。OS_SYS_CLOCK的默认值基于硬件平台配置。
- 调用时钟转换/统计接口。
说明:
- 时间管理不是单独的功能模块,依赖于OS_SYS_CLOCK和LOSCFG_BASE_CORE_TICK_PER_SECOND两个配置选项。
- 系统的Tick数在关中断的情况下不进行计数,故系统Tick数不能作为准确时间使用。
- 上文描述的配置选项维护在开发板工程 target_config.h 中,部分配置项未定义的缺省值定义在内核 los_config.h中。
编程实例
实例描述
在下面的例子中,介绍了时间管理的基本方法,包括:
- 时间转换:将毫秒数转换为Tick数,或将Tick数转换为毫秒数。
- 时间统计:每Tick的Cycle数、自系统启动以来的Tick数和延迟后的Tick数。
示例代码
前提条件:
- 使用每秒的Tick数LOSCFG_BASE_CORE_TICK_PER_SECOND的默认值100。
- 配好OS_SYS_CLOCK系统主时钟频率。
时间转换:
本演示代码在 ./kernel/liteos_m/testsuites/src/osTest.c 中编译验证,在TestTaskEntry中调用验证入口函数ExampleTransformTime和ExampleGetTime。
VOID ExampleTransformTime(VOID)
{
UINT32 ms;
UINT32 tick;
/* 10000ms转换为tick */
tick = LOS_MS2Tick(10000);
printf("tick = %d \n", tick);
/* 100tick转换为ms */
ms = LOS_Tick2MS(100);
printf("ms = %d \n", ms);
}
时间统计和时间延迟:
VOID ExampleGetTime(VOID)
{
UINT32 cyclePerTick;
UINT64 tickCountBefore;
UINT64 tickCountAfter;
cyclePerTick = LOS_CyclePerTickGet();
if (0 != cyclePerTick) {
printf("LOS_CyclePerTickGet = %d \n", cyclePerTick);
}
tickCountBefore = LOS_TickCountGet();
LOS_TaskDelay(200);
tickCountAfter = LOS_TickCountGet();
printf("LOS_TickCountGet after delay rising = %d \n", (UINT32)(tickCountAfter - tickCountBefore));
}
结果验证
编译运行得到的结果为:
时间转换:
tick = 1000
ms = 1000
时间统计和时间延迟:
LOS_CyclePerTickGet = 250000 (根据实际运行环境,数据会有差异)
LOS_TickCountGet after delay rising = 200
软件定时器
基本概念
软件定时器,是基于系统Tick时钟中断且由软件来模拟的定时器,当经过设定的Tick时钟计数值后会触发用户定义的回调函数。定时精度与系统Tick时钟的周期有关。
硬件定时器受硬件的限制,数量上不足以满足用户的实际需求,因此为了满足用户需求,提供更多的定时器,OpenHarmony LiteOS-M内核提供软件定时器功能。软件定时器扩展了定时器的数量,允许创建更多的定时业务。
软件定时器功能上支持:
- 静态裁剪:能通过宏关闭软件定时器功能。
- 软件定时器创建。
- 软件定时器启动。
- 软件定时器停止。
- 软件定时器删除。
- 软件定时器剩余Tick数获取。
运行机制
软件定时器是系统资源,在模块初始化的时候已经分配了一块连续的内存,系统支持的最大定时器个数由los_config.h中的LOSCFG_BASE_CORE_SWTMR_LIMIT宏配置,该值按产品实际需要设定。
软件定时器使用了系统的一个队列和一个任务资源,软件定时器的触发遵循队列规则,先进先出。定时时间短的定时器总是比定时时间长的靠近队列头,满足优先被触发的准则。
软件定时器以Tick为基本计时单位,当用户创建并启动一个软件定时器时,OpenHarmony LiteOS-M内核会根据当前系统Tick时间及用户设置的定时间隔确定该定时器的到期Tick时间,并将该定时器控制结构挂入计时全局链表。
当Tick中断到来时,在Tick中断处理函数中扫描软件定时器的计时全局链表,看是否有定时器超时,若有则将超时的定时器记录下来。
Tick中断处理函数结束后,软件定时器任务(优先级为最高)被唤醒,在该任务中调用之前记录下来的定时器的超时回调函数。
定时器状态
- OS_SWTMR_STATUS_UNUSED(未使用) 系统在定时器模块初始化的时候将系统中所有定时器资源初始化成该状态。
- OS_SWTMR_STATUS_CREATED(创建未启动/停止) 在未使用状态下调用LOS_SwtmrCreate接口或者启动后调用LOS_SwtmrStop接口后,定时器将变成该状态。
- OS_SWTMR_STATUS_TICKING(计数) 在定时器创建后调用LOS_SwtmrStart接口,定时器将变成该状态,表示定时器运行时的状态。
定时器模式
OpenHarmony LiteOS-M内核的软件定时器提供三类定时器机制:
- 第一类是单次触发定时器,这类定时器在启动后只会触发一次定时器事件,然后定时器自动删除。
- 第二类是周期触发定时器,这类定时器会周期性的触发定时器事件,直到用户手动地停止定时器,否则将永远持续执行下去。
- 第三类也是单次触发定时器,但与第一类不同之处在于这类定时器超时后不会自动删除,需要调用定时器删除接口删除定时器。
接口说明
OpenHarmony LiteOS-M内核的软件定时器模块提供下面几种功能,接口详细信息可以查看API参考。
表1 软件定时器接口
功能分类 | 接口描述 |
创建、删除定时器 | - LOS_SwtmrCreate:创建定时器。 - LOS_SwtmrDelete:删除定时器。 |
启动、停止定时器 | - LOS_SwtmrStart:启动定时器。 - LOS_SwtmrStop:停止定时器。 |
获得软件定时器剩余Tick数 | LOS_SwtmrTimeGet:获得软件定时器剩余Tick数。 |
开发流程
软件定时器的典型开发流程:
- 配置软件定时器。
- 确认配置项LOSCFG_BASE_CORE_SWTMR和LOSCFG_BASE_IPC_QUEUE为1打开状态;
- 配置LOSCFG_BASE_CORE_SWTMR_LIMIT最大支持的软件定时器数;
- 配置OS_SWTMR_HANDLE_QUEUE_SIZE软件定时器队列最大长度;
- 创建定时器LOS_SwtmrCreate。
- 创建一个指定计时时长、指定超时处理函数、指定触发模式的软件定时器;
- 返回函数运行结果,成功或失败;
- 启动定时器LOS_SwtmrStart。
- 获得软件定时器剩余Tick数LOS_SwtmrTimeGet。
- 停止定时器LOS_SwtmrStop。
- 删除定时器LOS_SwtmrDelete。
说明:
- 软件定时器的回调函数中不要做过多操作,不要使用可能引起任务挂起或者阻塞的接口或操作。
- 软件定时器使用了系统的一个队列和一个任务资源,软件定时器任务的优先级设定为0,且不允许修改 。
- 系统可配置的软件定时器资源个数是指:整个系统可使用的软件定时器资源总个数,而并非是用户可使用的软件定时器资源个数。例如:系统软件定时器多占用一个软件定时器资源数,那么用户能使用的软件定时器资源就会减少一个。
- 创建单次软件定时器,该定时器超时执行完回调函数后,系统会自动删除该软件定时器,并回收资源。
- 创建单次不自删除属性的定时器,用户需要调用定时器删除接口删除定时器,回收定时器资源,避免资源泄露。
编程实例
实例描述
在下面的例子中,演示如下功能:
- 软件定时器创建、启动、删除、暂停、重启操作。
- 单次软件定时器,周期软件定时器使用方法。
示例代码
前提条件:
- 在los_config.h中,将LOSCFG_BASE_CORE_SWTMR配置项打开。
- 在los_config.h中,将LOSCFG_BASE_CORE_SWTMR_ALIGN配置项关闭,示例代码中演示代码不涉及定时器对齐。
- 配置好LOSCFG_BASE_CORE_SWTMR_LIMIT最大支持的软件定时器数。
- 配置好OS_SWTMR_HANDLE_QUEUE_SIZE软件定时器队列最大长度。
代码实现如下:
本演示代码在 ./kernel/liteos_m/testsuites/src/osTest.c 中编译验证,在TestTaskEntry中调用验证入口函数ExampleSwtmr。
#include "los_swtmr.h"
/* 定时器间隔时间 */
#define SWTMR_INTERVAL_LONG 1000
#define SWTMR_INTERVAL_SHORT 100
/* 定时器触发次数计数 */
UINT32 g_timerCount1 = 0;
UINT32 g_timerCount2 = 0;
/* 回调函数1,单次触发定时器的回调函数 */
void Timer1Callback(UINT32 arg)
{
g_timerCount1++;
printf("g_timerCount1=%d\n", g_timerCount1);
}
/* 回调函数2,多次触发定时器的回调函数 */
void Timer2Callback(UINT32 arg)
{
g_timerCount2++;
printf("g_timerCount2=%d\n", g_timerCount2);
}
void SwtmrTest(void)
{
UINT32 ret;
UINT32 id1; // 定时器id1,单次触发定时器
UINT32 id2; // 定时器id2,周期触发定时器
UINT32 tickCount;
#if (LOSCFG_BASE_CORE_SWTMR_ALIGN == 1)
/* 创建单次软件定时器,Tick数为1000,启动到1000Tick数时执行回调函数1 */
LOS_SwtmrCreate(SWTMR_INTERVAL_LONG, LOS_SWTMR_MODE_ONCE, Timer1Callback, &id1, 0,
OS_SWTMR_ROUSES_IGNORE, OS_SWTMR_ALIGN_SENSITIVE);
/* 创建周期性软件定时器,每100Tick数执行回调函数2 */
LOS_SwtmrCreate(SWTMR_INTERVAL_SHORT, LOS_SWTMR_MODE_PERIOD, Timer2Callback, &id2, 0,
OS_SWTMR_ROUSES_IGNORE, OS_SWTMR_ALIGN_SENSITIVE);
#else
/* 创建单次软件定时器,Tick数为1000,启动到1000Tick数时执行回调函数1 */
LOS_SwtmrCreate(SWTMR_INTERVAL_LONG, LOS_SWTMR_MODE_ONCE, Timer1Callback, &id1, 0);
/* 创建周期性软件定时器,每100Tick数执行回调函数2 */
LOS_SwtmrCreate(SWTMR_INTERVAL_SHORT, LOS_SWTMR_MODE_PERIOD, Timer2Callback, &id2, 0);
#endif
/* 启动单次软件定时器 */
ret = LOS_SwtmrStart(id1);
printf("start Timer1 %s\n", (ret == LOS_OK) ? "success" : "failed");
/* 短时间延时,定时器还未触发 */
LOS_TaskDelay(SWTMR_INTERVAL_SHORT);
/* 单次定时器还未到时间触发,此时停止应该成功 */
ret = LOS_SwtmrStop(id1);
printf("stop timer1 %s\n", (ret == LOS_OK) ? "success" : "failed");
LOS_SwtmrStart(id1);
/* 长时间延时,定时器触发 */
LOS_TaskDelay(SWTMR_INTERVAL_LONG);
/* 单次定时器触发后自删除,此时停止失败才是正常 */
ret = LOS_SwtmrStop(id1);
printf("timer1 self delete test %s\n", (ret != LOS_OK) ? "success" : "failed");
/* 启动周期性软件定时器 */
ret = LOS_SwtmrStart(id2);
printf("start Timer2 %s\n", (ret == LOS_OK) ? "success" : "failed");
/* 长时间延时,定时器周期触发 */
LOS_TaskDelay(SWTMR_INTERVAL_LONG);
LOS_SwtmrStop(id2);
ret = LOS_SwtmrDelete(id2);
if (ret == LOS_OK) {
printf("delete Timer2 success\n");
}
}
UINT32 ExampleSwtmr(VOID)
{
UINT32 ret;
TSK_INIT_PARAM_S taskParam = { 0 };
UINT32 taskId;
/* 锁任务调度 */
LOS_TaskLock();
/* 创建任务 */
taskParam.pfnTaskEntry = (TSK_ENTRY_FUNC)SwtmrTest;
taskParam.pcName = "TimerTsk";
taskParam.uwStackSize = LOSCFG_BASE_CORE_TSK_DEFAULT_STACK_SIZE;
taskParam.usTaskPrio = 5;
ret = LOS_TaskCreate(&taskId, &taskParam);
if (ret != LOS_OK) {
printf("TimerTsk create failed.\n");
return LOS_NOK;
}
/* 解锁任务调度 */
LOS_TaskUnlock();
return LOS_OK;
}
结果验证
编译烧录运行,输出结果如下:
start Timer1 success
stop timer1 success
g_timerCount1=1
timer1 self delete test success
start Timer2 success
g_timerCount2=1
g_timerCount2=2
g_timerCount2=3
g_timerCount2=4
g_timerCount2=5
g_timerCount2=6
g_timerCount2=7
g_timerCount2=8
g_timerCount2=9
g_timerCount2=10
delete Timer2 success
双向链表
基本概念
双向链表是指含有往前和往后两个方向的链表,即每个结点中除存放下一个节点指针外,还增加一个指向前一个节点的指针。其头指针head是唯一确定的。
从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后继结点,这种数据结构形式使得双向链表在查找时更加方便,特别是大量数据的遍历。由于双向链表具有对称性,能方便地完成各种插入、删除等操作,但需要注意前后方向的操作。
功能说明
双向链表模块为用户提供下面几种功能,接口详细信息可以查看API参考。
功能分类 | 接口描述 |
初始化和删除链表 | LOS_ListInit:将指定双向链表节点初始化为双向链表。 LOS_DL_LIST_HEAD:定义一个双向链表节点并以该节点初始化为双向链表。 LOS_ListDelInit:删除指定的双向链表。 |
增加节点 | LOS_ListAdd:将指定节点插入到双向链表头端。 LOS_ListTailInsert:将指定节点插入到双向链表尾端。 |
删除节点 | LOS_ListDelete:将指定节点从链表中删除。 LOS_ListDelInit:将指定节点从链表中删除,并使用该节点初始化链表。 |
判断双向链表是否为空 | LOS_ListEmpty:判断链表是否为空。 |
获取结构体信息 | LOS_DL_LIST_ENTRY:获取包含链表的结构体地址,接口的第一个入参表示的是链表中的某个节点,第二个入参是要获取的结构体名称,第三个入参是链表在该结构体中的名称。 LOS_OFF_SET_OF:获取指定结构体内的成员相对于结构体起始地址的偏移量。 |
遍历双向链表 | LOS_DL_LIST_FOR_EACH:遍历双向链表。 LOS_DL_LIST_FOR_EACH_SAFE:遍历双向链表,并存储当前节点的后继节点用于安全校验。 |
遍历包含双向链表的结构体 | LOS_DL_LIST_FOR_EACH_ENTRY:遍历指定双向链表,获取包含该链表节点的结构体地址。 LOS_DL_LIST_FOR_EACH_ENTRY_SAFE:遍历指定双向链表,获取包含该链表节点的结构体地址,并存储包含当前节点的后继节点的结构体地址。 |
开发流程
双向链表的典型开发流程:
- 调用LOS_ListInit/LOS_DL_LIST_HEAD初始双向链表。
- 调用LOS_ListAdd向链表插入节点。
- 调用LOS_ListTailInsert向链表尾部插入节点。
- 调用LOS_ListDelete删除指定节点。
- 调用LOS_ListEmpty判断链表是否为空。
- 调用LOS_ListDelInit删除指定节点并以此节点初始化链表。
说明:
- 需要注意节点指针前后方向的操作。
- 链表操作接口,为底层接口,不对入参进行判空,需要使用者确保传参合法。
- 如果链表节点的内存是动态申请的,删除节点时,要注意释放内存。
编程实例
实例描述
本实例实现如下功能:
- 初始化双向链表。
- 增加节点。
- 删除节点。
- 测试操作是否成功。
示例代码
示例代码如下:
本演示代码在 ./kernel/liteos_m/testsuites/src/osTest.c 中编译验证,在TestTaskEntry中调用验证入口函数ExampleList。
#include "stdio.h"
#include "los_list.h"
STATIC UINT32 ExampleList(VOID)
{
LOS_DL_LIST listHead = {NULL,NULL};
LOS_DL_LIST listNode1 = {NULL,NULL};
LOS_DL_LIST listNode2 = {NULL,NULL};
/* 初始化链表 */
printf("Initial head\n");
LOS_ListInit(&listHead);
/* 添加节点1和节点2,并校验他们的相互关系 */
LOS_ListAdd(&listHead, &listNode1);
if (listNode1.pstNext == &listHead && listNode1.pstPrev == &listHead) {
printf("Add listNode1 success\n");
}
LOS_ListTailInsert(&listHead, &listNode2);
if (listNode2.pstNext == &listHead && listNode2.pstPrev == &listNode1) {
printf("Tail insert listNode2 success\n");
}
/* 删除两个节点 */
LOS_ListDelete(&listNode1);
LOS_ListDelete(&listNode2);
/* 确认链表为空 */
if (LOS_ListEmpty(&listHead)) {
printf("Delete success\n");
}
return LOS_OK;
}
结果验证
编译运行得到的结果为:
Initial head
Add listNode1 success
Tail insert listNode2 success
Delete success
文章转载自:https://docs.openharmony.cn/pages/v3.2Beta/zh-cn/device-dev/kernel/kernel-mini-basic-time.md/