鸿蒙轻内核M核源码分析系列十 软件定时器Swtmr 原创 精华
鸿蒙轻内核M核源码分析系列十 软件定时器Swtmr
软件定时器(Software Timer
)是基于系统Tick
时钟中断且由软件来模拟的定时器。当经过设定的Tick
数后,会触发用户自定义的回调函数。硬件定时器受硬件的限制,数量上不足以满足用户的实际需求。鸿蒙轻内核提供了软件定时器功能可以提供更多的定时器,满足用户需求。
本文通过分析鸿蒙轻内核定时器模块的源码,掌握定时器使用上的差异。本文中所涉及的源码,以OpenHarmony LiteOS-M
内核为例,均可以在开源站点https://gitee.com/openharmony/kernel_liteos_m 获取。
接下来,我们看下定时器的结构体,定时器初始化,定时器常用操作的源代码。
1、定时器结构体定义和常用宏定义
1.1 定时器结构体定义
在文件kernel\include\los_swtmr.h
定义的定时器控制块结构体为SWTMR_CTRL_S
,结构体源代码如下。定时器状态.ucState
取值OS_SWTMR_STATUS_UNUSED
、OS_SWTMR_STATUS_CREATED
或OS_SWTMR_STATUS_TICKING
,定时器模式.mode
取值LOS_SWTMR_MODE_ONCE
、LOS_SWTMR_MODE_PERIOD
或LOS_SWTMR_MODE_NO_SELFDELETE
。其他结构体成员的解释见注释部分。
另外,还对回调函数及其参数单独定义了一个结构体SwtmrHandlerItem
,如下:
1.2 定时器常用宏定义
定时器头文件kernel\include\los_swtmr.h
中还提供了相关的枚举和宏,从定时器池里获取定时器控制块的宏定义OS_SWT_FROM_SID
如下:
头文件中定义的定时器几个枚举如下:
2、定时器初始化
定时器在内核中默认开启,用户可以通过宏LOSCFG_BASE_CORE_SWTMR
进行关闭。开启定时器的情况下,在系统启动时,在kernel\src\los_init.c
中调用OsSwtmrInit()
进行定时器模块初始化。下面,我们分析下定时器初始化的代码。
⑴处如果开启定时器对齐宏LOSCFG_BASE_CORE_SWTMR_ALIGN
,清零g_swtmrAlignID
数组。定时器的数量由宏LOSCFG_BASE_CORE_SWTMR_LIMIT
定义,⑵处计算定时器池需要的内存大小,然后为定时器申请内存,如果申请失败,则返回错误。⑶初始化空闲定时器链表g_swtmrFreeList
,维护未使用的定时器。循环每一个定时器进行初始化,为每一个定时器节点指定索引timerId
,定时器控制块依次指向下一个定时器控制块。
⑷处代码为定时器创建队列,队列的消息大小OS_SWTMR_HANDLE_QUEUE_SIZE
等于定时器的数量LOSCFG_BASE_CORE_SWTMR_LIMIT
,消息内容的最大大小sizeof(SwtmrHandlerItem)
。后文分析定时器队列读取写入消息的时候具体来看是什么消息。⑸处调用函数OsSwtmrTaskCreate()
创建定时器任务,定时器任务优先级最高,任务的入口函数为OsSwtmrTask()
,后文会分析该函数。⑹处初始化定时器排序链表,源码分析系列之前的文章分析过,可以阅读下排序链表数据结构章节。⑺处注册定时器扫描函数OsSwtmrScan
。
我们再看一下定时器任务的入口函数为OsSwtmrTask()
。⑴进行for
永久循环,队列读取不到数据时会阻塞,因为优先级比较高,定时器队列有数据时该任务就会执行。从定时器队列中读取定时器处理函数地址放入指针地址&swtmrHandle
,读取的长度为sizeof(SwtmrHandlerItem)
。成功读取后,获取定时器回调函数及其参数,然后⑵处执行定时器回调函数。记录定时器回调函数的执行时间,⑶处判断执行时间是否超时,如果超时,打印警告信息。
3、定时器常用操作
3.1 定时器创建
我们分析下创建定时器函数LOS_SwtmrCreate()
的代码。先不考虑定时器对齐LOSCFG_BASE_CORE_SWTMR_ALIGN
的情况。先看下函数参数,interval
是定时器执行时间间隔,mode
是创建的定时器模式,handler
、arg
是定时器回调函数及其参数。swtmrId
是定时器编号。
⑴处对传入参数定时器超时间隔、定时器模式、回调函数,定时器编号进行校验。⑵判断空闲定时器池是否为空,为空则返回错误,无法创建定时器。⑶处如果定时器不为空,则获取定时器控制块swtmr
。⑷处对定时器控制块信息进行初始化。⑸处把该定时器排序链表节点的响应时间responseTime
初始化为-1。
3.2 定时器删除
我们可以使用函数LOS_SwtmrDelete(UINT32 swtmrId)
来删除定时器,下面通过分析源码看看如何删除定时器的。
⑴处判断定时器swtmrId
是否超过OS_SWTMR_MAX_TIMERID
,如果超过则返回错误码。如果定时器编号没有问题,获取定时器控制块LosSwtmrCB *swtmr
。⑵处判断要删除的定时器swtmrId
是否匹配,不匹配则返回错误码。⑶处判断定时器的状态,如果定时器定时器没有创建,不能删除。如果定时器计时中,需要先停止OsSwtmrStop(swtmr)
,然后再删除OsSwtmrDelete(swtmr)
。
接下来,我们继续看看如何调用函数OsSwtmrDelete(swtmr)
删除定时器。函数特别简单,把定时器放入空闲定时器链表g_swtmrFreeList
头部,然后把定时器状态改为未使用状态就完成了删除。
3.3 定时器启动
创建完毕定时器后,我们可以使用函数LOS_SwtmrStart(UINT32 swtmrId)
来启动定时器,下面通过分析源码看看如何启动定时器的。
⑴处判断定时器swtmrId
是否超过OS_SWTMR_MAX_TIMERID
,如果超过则返回错误码。如果定时器编号没有问题,获取定时器控制块LosSwtmrCB *swtmr
。⑵处判断要启动的定时器swtmrId
是否匹配,不匹配则返回错误码。⑶处判断定时器的状态,如果定时器定时器没有创建,不能启动。如果定时器计时中,需要先停止OsSwtmrStop(swtmr)
,然后再启动OsSwtmrStart(swtmr)
。
接下来,我们继续看看如何调用函数OsSwtmrStart(swtmr)
启动定时器。函数特别简单,⑴设置定时器的等待超时时间,并把定时器状态改为计时中。⑵处把该定时器插入超时排序链表中。如果已使能任务调度,则修改过期时间。
3.4 定时器停止
我们可以使用函数LOS_SwtmrStop(UINT32 swtmrId)
来停止定时器,下面通过分析源码看看如何停止定时器的。
⑴处判断定时器swtmrId
是否超过OS_SWTMR_MAX_TIMERID
,如果超过则返回错误码。如果定时器编号没有问题,获取定时器控制块LosSwtmrCB *swtmr
。⑵处判断要启动的定时器swtmrId
是否匹配,不匹配则返回错误码。⑶处判断定时器的状态,如果定时器定时器没有创建,没有启动,不能停止。如果定时器计时中,会继续调用OsSwtmrStop(swtmr)
停止定时器。
接下来,我们继续看看如何调用函数OsSwtmrStop(swtmr)
停止定时器。函数特别简单,⑴处从排序链表中删除该定时器的排序链表节点,更改定时器的状态。⑵如果已使能任务调度,则修改过期时间。
4、定时器和Tick时间关系
定时器加入到超时排序链表后,随时时间一个tick
一个tick
的逝去,需要不断的检查定时器是否超时到期。从之前的文章,已经知道系统每走过一个tick
,系统就会调用一次Tick
中断的处理函数OsTickHandler()
,该函数会调用定时器扫描函数OsSwtmrScan()
来扫描、更新定时器时间。我们看下OsSwtmrScan()
的代码。
⑴处获取超时排序链表的链表节点listObject
,⑵判断排序链表是否为空,为空则返回。⑶获取排序链表的下一个链表节点sortList
。⑷循环遍历超时排序链表上响应时间小于等于当前时间的链表节点,意味着定时器到期,需要处理定时器的回调函数。⑸从超时排序链表中删除超时的节点,⑹获取定时器控制块SWTMR_CTRL_S *swtmr
,调用函数OsSwtmrTimeoutHandle(swtmr)
执行定时器回调函数,并设置需要调度的标记needSchedule
。⑺如果超时排序链表为空则终止循环。
我们最后看下函数OsSwtmrTimeoutHandle()
。⑴处把定时器回调函数写入定时器队列。⑵如果是一次性定时器,会把这个定时器删除,回收到空闲定时器链表,状态设置为未使用状态,然后更新定时器的编号timerId
。⑶如果定时器属于周期性定时器,重新启动定时器。⑷如果是一次性定时器但不删除,则把定时器状态设置为创建状态。
小结
本文带领大家一起剖析了鸿蒙轻内核的定时器模块的源代码,包含定时器的结构体、定时器池初始化、定时器创建、删除、启动停止等。感谢阅读,如有任何问题、建议,都可以留言给我们: https://gitee.com/openharmony/kernel_liteos_m/issues 。为了更容易找到鸿蒙轻内核代码仓,建议访问 https://gitee.com/openharmony/kernel_liteos_m ,关注Watch
、点赞Star
、并Fork
到自己账户下,谢谢。
新的一周从学习楼主文章开始