OpenHarmony设备开发 小型系统内核(LiteOS-A) 基础内核
版本:V3.2Beta
时间管理
基本概念
时间管理以系统时钟为基础。时间管理提供给应用程序所有和时间有关的服务。系统时钟是由定时/计数器产生的输出脉冲触发中断而产生的,一般定义为整数或长整数。输出脉冲的周期叫做一个“时钟滴答”。
系统时钟也称为时标或者Tick。一个Tick的时长可以静态配置。用户是以秒、毫秒为单位计时,而操作系统时钟计时是以Tick为单位的,当用户需要对系统操作时,例如任务挂起、延时等,输入秒为单位的数值,此时需要时间管理模块对二者进行转换。
Tick与秒之间的对应关系可以配置。
- Cycle系统最小的计时单位。Cycle的时长由系统主频决定,系统主频就是每秒钟的Cycle数。
- TickTick是操作系统的基本时间单位,对应的时长由系统主频及每秒Tick数决定,由用户配置。
OpenHarmony系统的时间管理模块提供时间转换、统计、延迟功能以满足用户对时间相关需求的实现。
开发指导
用户需要了解当前系统运行的时间以及Tick与秒、毫秒之间的转换关系,以及需要使用到时间管理模块的接口。
接口说明
OpenHarmony LiteOS-A内核的时间管理提供以下几种功能,接口详细信息可查看API参考。
表1 时间管理相关接口说明:
功能分类 | 接口描述 |
时间转换 | LOS_MS2Tick:毫秒转换成Tick LOS_Tick2MS:Tick转换成毫秒 |
时间统计 | LOS_TickCountGet:获取当前Tick数 LOS_CyclePerTickGet:每个Tick的cycle数 |
开发流程
- 调用时间转换接口;
- 获取系统Tick数完成时间统计等。
说明:
- 获取系统Tick数需要在系统时钟使能之后。
- 时间管理不是单独的功能模块,依赖于los_config.h中的OS_SYS_CLOCK和LOSCFG_BASE_CORE_TICK_PER_SECOND两个配置选项。
- 系统的Tick数在关中断的情况下不进行计数,故系统Tick数不能作为准确时间计算。
编程实例
前置条件:
- 配置好LOSCFG_BASE_CORE_TICK_PER_SECOND,即系统每秒的Tick数,范围(0, 1000)。
- 配置好OS_SYS_CLOCK 系统时钟频率,单位:Hz。
示例代码
时间转换:
VOID Example_TransformTime(VOID)
{
UINT32 uwMs;
UINT32 uwTick;
uwTick = LOS_MS2Tick(10000); //10000 ms数转换为Tick数
PRINTK("uwTick = %d \n",uwTick);
uwMs= LOS_Tick2MS(100); //100 Tick数转换为ms数
PRINTK("uwMs = %d \n",uwMs);
}
时间统计和时间延迟:
VOID Example_GetTime(VOID)
{
UINT32 uwcyclePerTick;
UINT64 uwTickCount;
uwcyclePerTick = LOS_CyclePerTickGet(); //每个Tick多少Cycle数
if(0 != uwcyclePerTick)
{
PRINTK("LOS_CyclePerTickGet = %d \n", uwcyclePerTick);
}
uwTickCount = LOS_TickCountGet(); //获取Tick数
if(0 != uwTickCount)
{
PRINTK("LOS_TickCountGet = %d \n", (UINT32)uwTickCount);
}
LOS_TaskDelay(200);//延迟200 Tick
uwTickCount = LOS_TickCountGet();
if(0 != uwTickCount)
{
PRINTK("LOS_TickCountGet after delay = %d \n", (UINT32)uwTickCount);
}
}
结果验证
编译运行的结果如下:
时间转换:
uwTick = 10000
uwMs = 100
时间统计和时间延迟:
LOS_CyclePerTickGet = 49500
LOS_TickCountGet = 347931
LOS_TickCountGet after delay = 348134
软件定时器
基本概念
软件定时器,是基于系统Tick时钟中断且由软件来模拟的定时器,当经过设定的Tick时钟计数值后会触发用户定义的回调函数。定时精度与系统Tick时钟的周期有关。
硬件定时器受硬件的限制,数量上不足以满足用户的实际需求,因此为了满足用户需求,提供更多的定时器,OpenHarmony LiteOS-A内核提供软件定时器功能。
软件定时器扩展了定时器的数量,允许创建更多的定时业务。
软件定时器支持以下功能:
- 静态裁剪:能通过宏关闭软件定时器功能。
- 软件定时器创建。
- 软件定时器启动。
- 软件定时器停止。
- 软件定时器删除。
- 软件定时器剩余Tick数获取。
运行机制
软件定时器是系统资源,在模块初始化的时候已经分配了一块连续的内存,系统支持的最大定时器个数由los_config.h中的LOSCFG_BASE_CORE_SWTMR_LIMIT宏配置。
软件定时器使用了系统的一个队列和一个任务资源,软件定时器的触发遵循队列规则,先进先出。同一时刻设置的定时时间短的定时器总是比定时时间长的靠近队列头,满足优先被触发的准则。
软件定时器以Tick为基本计时单位,当用户创建并启动一个软件定时器时,OpenHarmony系统会根据当前系统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-A内核的软件定时器模块提供以下几种功能。
表1 软件定时器接口说明
功能分类 | 接口描述 |
创建、删除定时器 | LOS_SwtmrCreate:创建软件定时器 LOS_SwtmrDelete:删除软件定时器 |
启动、停止定时器 | LOS_SwtmrStart:启动软件定时器 LOS_SwtmrStop:停止软件定时器 |
获得软件定时剩余Tick数 | LOS_SwtmrTimeGet:获得软件定时器剩余Tick数 |
开发流程
软件定时器的典型开发流程:
- 配置软件定时器。
- 确认配置项LOSCFG_BASE_CORE_SWTMR和LOSCFG_BASE_IPC_QUEUE为打开状态;
- 配置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配置项打开。
- 配置好LOSCFG_BASE_CORE_SWTMR_LIMIT最大支持的软件定时器数。
- 配置好OS_SWTMR_HANDLE_QUEUE_SIZE软件定时器队列最大长度。
编程示例
#include "los_swtmr.h"
void Timer1_Callback(uint32_t arg);
void Timer2_Callback(uint32_t arg);
UINT32 g_timercount1 = 0;
UINT32 g_timercount2 = 0;
void Timer1_Callback(uint32_t arg) // 回调函数1
{
unsigned long tick_last1;
g_timercount1++;
tick_last1=(UINT32)LOS_TickCountGet(); // 获取当前Tick数
PRINTK("g_timercount1=%d\n",g_timercount1);
PRINTK("tick_last1=%d\n",tick_last1);
}
void Timer2_Callback(uint32_t arg) // 回调函数2
{
unsigned long tick_last2;
tick_last2=(UINT32)LOS_TickCountGet();
g_timercount2 ++;
PRINTK("g_timercount2=%d\n",g_timercount2);
PRINTK("tick_last2=%d\n",tick_last2);
}
void Timer_example(void)
{
UINT16 id1;
UINT16 id2; // timer id
UINT32 uwTick;
/* 创建单次软件定时器,Tick数为1000,启动到1000Tick数时执行回调函数1 */
LOS_SwtmrCreate (1000, LOS_SWTMR_MODE_ONCE, Timer1_Callback, &id1, 1);
/* 创建周期性软件定时器,每100Tick数执行回调函数2 */
LOS_SwtmrCreate(100, LOS_SWTMR_MODE_PERIOD, Timer2_Callback, &id2, 1);
PRINTK("create Timer1 success\n");
LOS_SwtmrStart (id1); //启动单次软件定时器
dprintf("start Timer1 success\n");
LOS_TaskDelay(200); // 延时200Tick数
LOS_SwtmrTimeGet(id1, &uwTick); // 获得单次软件定时器剩余Tick数
PRINTK("uwTick =%d\n", uwTick);
LOS_SwtmrStop(id1); // 停止软件定时器
PRINTK("stop Timer1 success\n");
LOS_SwtmrStart(id1);
LOS_TaskDelay(1000);
LOS_SwtmrDelete(id1); // 删除软件定时器
PRINTK("delete Timer1 success\n");
LOS_SwtmrStart(id2); // 启动周期性软件定时器
PRINTK("start Timer2\n");
LOS_TaskDelay(1000);
LOS_SwtmrStop(id2);
LOS_SwtmrDelete(id2);
}
运行结果
create Timer1 success
start Timer1 success
uwTick =800
stop Timer1 success
g_timercount1=1
tick_last1=1201
delete Timer1 success
start Timer2
g_timercount2 =1
tick_last1=1301
g_timercount2 =2
tick_last1=1401
g_timercount2 =3
tick_last1=1501
g_timercount2 =4
tick_last1=1601
g_timercount2 =5
tick_last1=1701
g_timercount2 =6
tick_last1=1801
g_timercount2 =7
tick_last1=1901
g_timercount2 =8
tick_last1=2001
g_timercount2 =9
tick_last1=2101
g_timercount2 =10
tick_last1=2201
原子操作
基本概念
在支持多任务的操作系统中,修改一块内存区域的数据需要“读取-修改-写入”三个步骤。然而同一内存区域的数据可能同时被多个任务访问,如果在修改数据的过程中被其他任务打断,就会造成该操作的执行结果无法预知。
使用开关中断的方法固然可以保证多任务执行结果符合预期,但是显然这种方法会影响系统性能。
ARMv6架构引入了LDREX和STREX指令,以支持对共享存储器更缜密的非阻塞同步。由此实现的原子操作能确保对同一数据的“读取-修改-写入”操作在它的执行期间不会被打断,即操作的原子性。
运行机制
OpenHarmony系统通过对ARMv6架构中的LDREX和STREX进行封装,向用户提供了一套原子性的操作接口。
● LDREX Rx, [Ry] 读取内存中的值,并标记对该段内存的独占访问:
● 读取寄存器Ry指向的4字节内存数据,保存到Rx寄存器中。
● 对Ry指向的内存区域添加独占访问标记。
● STREX Rf, Rx, [Ry] 检查内存是否有独占访问标记,如果有则更新内存值并清空标记,否则不更新内存:
● 有独占访问标记
● 将寄存器Rx中的值更新到寄存器Ry指向的内存。
● 标志寄存器Rf置为0。
● 没有独占访问标记
● 不更新内存。
● 标志寄存器Rf置为1。
● 判断标志寄存器
● 标志寄存器为0时,退出循环,原子操作结束。
● 标志寄存器为1时,继续循环,重新进行原子操作。
开发指导
接口说明
OpenHarmony LiteOS-A内核的原子操作模块提供以下几种功能。
表1 原子操作接口说明
功能分类 | 接口名称 | 描述 |
读 | LOS_AtomicRead | 读取32bit原子数据 |
读 | LOS_Atomic64Read | 读取64bit原子数据 |
写 | LOS_AtomicSet | 设置32bit原子数据 |
写 | LOS_Atomic64Set | 设置64bit原子数据 |
加 | LOS_AtomicAdd | 对32bit原子数据做加法 |
加 | LOS_Atomic64Add | 对64bit原子数据做加法 |
加 | LOS_AtomicInc | 对32bit原子数据做加1 |
加 | LOS_Atomic64Inc | 对64bit原子数据做加1 |
加 | LOS_AtomicIncRet | 对32bit原子数据做加1并返回 |
加 | LOS_Atomic64IncRet | 对64bit原子数据做加1并返回 |
减 | LOS_AtomicSub | 对32bit原子数据做减法 |
减 | LOS_Atomic64Sub | 对64bit原子数据做减法 |
减 | LOS_AtomicDec | 对32bit原子数据做减1 |
减 | LOS_Atomic64Dec | 对64bit原子数据做减1 |
减 | LOS_AtomicDecRet | 对32bit原子数据做减1并返回 |
减 | LOS_Atomic64DecRet | 对64bit原子数据做减1并返回 |
交换 | LOS_AtomicXchgByte | 交换8bit内存数据 |
交换 | LOS_AtomicXchg16bits | 交换16bit内存数据 |
交换 | LOS_AtomicXchg32bits | 交换32bit内存数据 |
交换 | LOS_AtomicXchg64bits | 交换64bit内存数据 |
先比较后交换 | LOS_AtomicCmpXchgByte | 比较相同后交换8bit内存数据 |
先比较后交换 | LOS_AtomicCmpXchg16bits | 比较相同后交换16bit内存数据 |
先比较后交换 | LOS_AtomicCmpXchg32bits | 比较相同后交换32bit内存数据 |
先比较后交换 | LOS_AtomicCmpXchg64bits | 比较相同后交换64bit内存数据 |
开发流程
有多个任务对同一个内存数据进行加减或交换等操作时,使用原子操作保证结果的可预知性。
说明:
原子操作接口仅支持整型数据。
编程实例
实例描述
调用原子操作相关接口,观察结果:
- 创建两个任务
- 任务一用LOS_AtomicInc对全局变量加100次。
- 任务二用LOS_AtomicDec对全局变量减100次。
- 子任务结束后在主任务中打印全局变量的值。
示例代码
示例代码如下:
#include "los_hwi.h"
#include "los_atomic.h"
#include "los_task.h"
UINT32 g_testTaskId01;
UINT32 g_testTaskId02;
Atomic g_sum;
Atomic g_count;
UINT32 Example_Atomic01(VOID)
{
int i = 0;
for(i = 0; i < 100; ++i) {
LOS_AtomicInc(&g_sum);
}
LOS_AtomicInc(&g_count);
return LOS_OK;
}
UINT32 Example_Atomic02(VOID)
{
int i = 0;
for(i = 0; i < 100; ++i) {
LOS_AtomicDec(&g_sum);
}
LOS_AtomicInc(&g_count);
return LOS_OK;
}
UINT32 Example_AtomicTaskEntry(VOID)
{
TSK_INIT_PARAM_S stTask1={0};
stTask1.pfnTaskEntry = (TSK_ENTRY_FUNC)Example_Atomic01;
stTask1.pcName = "TestAtomicTsk1";
stTask1.uwStackSize = LOSCFG_BASE_CORE_TSK_DEFAULT_STACK_SIZE;
stTask1.usTaskPrio = 4;
stTask1.uwResved = LOS_TASK_STATUS_DETACHED;
TSK_INIT_PARAM_S stTask2={0};
stTask2.pfnTaskEntry = (TSK_ENTRY_FUNC)Example_Atomic02;
stTask2.pcName = "TestAtomicTsk2";
stTask2.uwStackSize = LOSCFG_BASE_CORE_TSK_DEFAULT_STACK_SIZE;
stTask2.usTaskPrio = 4;
stTask2.uwResved = LOS_TASK_STATUS_DETACHED;
LOS_TaskLock();
LOS_TaskCreate(&g_testTaskId01, &stTask1);
LOS_TaskCreate(&g_testTaskId02, &stTask2);
LOS_TaskUnlock();
while(LOS_AtomicRead(&g_count) != 2);
PRINTK("g_sum = %d\n", g_sum);
return LOS_OK;
}
结果验证
g_sum = 0
文章转载自:https://docs.openharmony.cn/pages/v3.2Beta/zh-cn/device-dev/kernel/kernel-small-basic-time.md/