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。此模块提供了定时器基本的创建、启动、停止、删除等功能,使用时请按照上面的接口限制来正确使用。