物联网操作系统AliOS Things探索与实践
上QQ阅读APP看书,第一时间看更新

2.5 定时器(timer)

2.5.1 基本概念

tick一般是作为任务延迟调度的内部机制,其接口主要为系统内部使用。对于使用操作系统的应用软件,也需要定时触发相关功能的接口,包括单次定时器和周期定时器。从用户层面来讲,用户不关注底层CPU的定时机制以及tick的调度,他们希望的定时器接口是可以创建和使能一个软件接口定时器,时间到了之后,用户的钩子函数能被执行。而对于操作系统的定时器本身来讲,也需要屏蔽底层定时模块的差异。因此,在软件层面上,对于定时器硬件相关的操作由tick模块完成,定时器模块基于tick作为最基本的时间调度单元,即最小时间周期,来推动自身时间轴的运行。

Rhino提供基本的软件定时器功能,包括定时器的创建、删除、运行,以及单次和周期定时器。

2.5.2 定时器的实现原理

操作系统需要完成定时器的两个功能:一个是定时器的管理;另一个是定时器的运行。

1.定时器的管理

定时器的管理主要包括定时器的创建、删除、启动、停止以及参数变更。在多任务系统中,对于共享资源,比如同一个定时器的操作都需要保证互斥,这就需要操作系统在管理定时器时增加关中断或者加锁操作。本操作系统通过命令buffer缓冲的方式实现了定时器管理的免锁机制,提高了管理和运行效率。其实现机制是使用操作系统本身的buf_queue机制,集成在定时器管理中的处理机制可以参看图2-7。

图2-7 定时器的管理机制

定时器管理接口如创建或者删除定时器接口被调用后,向命令buffer写入命令,设置需要配置的定时器、参数相关信息。timer任务循环从命令buffer中读取信息处理。

2.定时器的运行

timer任务除了处理管理命令外,还需要进行当前所有已运行定时器的实时调度。所有正在运行的timer都会被挂接在g_timer_head队列,timer任务循环从g_timer_head中取出时间最近一次的定时器,通过当前tick计数和该定时器的超时tick数来判断是否到定时时间,如果该定时器触发时间已到,则立即执行其处理函数;否则代表最近一次的定时器触发时间尚未到来,则在此段尚未到达时间之内,继续从命令buffer中收取消息,直到定时器触发时间到来后,再立即执行定时器处理函数。

上述流程在g_timer_head中有待处理定时器时才会进入,如果没有待处理定时器,定时器任务将只会进入定时器管理循环中。

2.5.3 定时器模块初始化

使用timer模块,首先需要确保k_config.h中宏RHINO_CONFIG_TIMER已打开。其模块初始化函数只有一个接口:void ktimer_init(void),此接口主要完成以下三个工作:

(1)初始化timer队列g_timer_head;

(2)初始化timer定时器管理buffer队列g_timer_queue;

(3)创建定时器基本处理任务g_timer_task。

2.5.4 定时器基本接口

1.定时器创建

静态创建函数原型:

      kstat_t krhino_timer_create(ktimer_t *timer, const name_t *name, timer_cb_t cb,
                      sys_time_t first, sys_time_t round, void *arg, uint8_t auto_run)

其中的主要参数意义分别为:

timer:用户传入定时器管理句柄;

cb:定时器处理钩子;

first:第一次延时时间;

round:后续周期定时时间;

auto_run:是否立即运行,不需要另外调用start。

动态创建函数原型:

      kstat_t krhino_timer_dyn_create(ktimer_t **timer, const name_t *name, timer_
  cb_t cb, sys_time_t first, sys_time_t round, void *arg, uint8_t auto_run)

其与静态创建函数的主要区别在于timer是出参,内存不需要用户指定,由内部申请返回。

定时器基本接口的代码样例及其简单说明如下所示:

    ret=krhino_timer_create(&g_timer, "g_timer", timer_handler,1 ,0, NULL,0);

上述样例表示创建名字为g_timer的单次定时器,定时时间为1个tick。

    ret=krhino_timer_create(&g_timer, "g_timer", timer_handler,1 ,10, NULL,0);

上述样例表示创建名字为g_timer的周期定时器,首次定时时间为1个tick,后续定时周期为10个tick。

      ret=krhino_timer_create(&g_timer, "g_timer", timer_handler,1 ,10 , NULL,1);

上述样例表示创建名字为g_timer的周期定时器,首次定时时间为1个tick,后续定时周期为10个tick,并且立即执行,不需要额外调用start。

2.定时器删除

静态删除函数原型:

      kstat_t krhino_timer_del(ktimer_t *timer)

动态删除函数原型:

      kstat_t krhino_timer_dyn_del(ktimer_t *timer)

krhino_timer_del只能释放krhino_timer_create创建的定时器;krhino_timer_dyn_del只能释放krhino_timer_dyn_create创建的定时器。动态删除接口会释放timer内存。

3.定时器启动和停止

启动接口:

      kstat_t krhino_timer_start(ktimer_t *timer)

停止接口:

      kstat_t krhino_timer_stop(ktimer_t *timer)

除了在start阶段设置timer为自动启动外,其他定时器都需要调用krhino_timer_start来运行timer,并将该定时器加入g_timer_head队列;同理,krhino_timer_stop会将其从g_timer_head队列中删除。

4.参数变更接口

定时时长变更接口:

      kstat_t krhino_timer_change (ktimer_t *timer, sys_time_t first, sys_time_t
  round)

此接口允许修改初次和周期定时时长。

接口限制:需要在定时器处于未启动状态时才能修改。

参数变更接口:

      kstat_t krhino_timer_arg_change(ktimer_t *timer, void *arg)

此函数修改定时器触发时,传入钩子函数的参数。

接口限制:需要在定时器处于未启动状态时才能修改。

自动参数变更接口:

      kstat_t krhino_timer_arg_change_auto(ktimer_t *timer, void *arg)

此接口会完成定时器停止、参数修改、重新启动等一连串动作。

5.总结

一般来说,tick负责内部任务的调度,因此是内核模块的必选项;timer是基于tick单元虚拟的软件定时器模块,如果用户不使用此模块,可以修改对应k_config.h的宏RHINO_CONFIG_TIMER。此模块提供了定时器基本的创建、启动、停止、删除等功能,使用时请按照上面的接口限制来正确使用。