前面分析完系统提供的硬件定时器入口时发现有宏RT_USING_TIMER_SOFT包裹的部分,这就意味着,RTT除了提供系统级的硬件定时器的基础上,还提供了一套软件定时器实现。此实现需要在Kconfig中开启宏RT_USING_TIMER_SOFT来实现。
软件定时器实现解析
软件定时器的启用入口
为何不讲注册入口,是因为启用入口包含了注册入口所需的参数,具体如下:
rt_err_t rt_timer_start(rt_timer_t timer) { //首部启用部分省略,和硬件定时器一致 ... #ifdef RT_USING_TIMER_SOFT if (timer->parent.flag & RT_TIMER_FLAG_SOFT_TIMER) {// 注册时需置标记位RT_TIMER_FLAG_SOFT_TIMER启用软件定时器 timer_list = _soft_timer_list; spinlock = &_stimer_lock; } else #endif /* RT_USING_TIMER_SOFT */ { timer_list = _timer_list; spinlock = &_htimer_lock; } //中间启用部分省略,和硬件定时器一致 ... #ifdef RT_USING_TIMER_SOFT if (err == RT_EOK && (timer->parent.flag & RT_TIMER_FLAG_SOFT_TIMER)) { // 释放信号量,也就意味着,软件定时器的执行,有依赖信号量的部分 rt_sem_release(&_soft_timer_sem); } #endif /* RT_USING_TIMER_SOFT */ // 尾部省略,与硬件定时器一致 ... return err; }
经过对使能部分的分析,其实我们可以看出,软件定时器的注册和创建方法,与硬件定时器是一致的,唯一的区别为,函数传入的flag标记需增加RT_TIMER_FLAG_SOFT_TIMER标记位。
软件定时器对应用的其他入口
软件定时器对应用的其他入口都与硬件定时器一致,这里就没必要再贴代码分析了,直接看硬件定时器部分即可。
软件定时器的执行解析
软件定时器系统注册入口
说来也奇怪,软件定时器的系统注册入口,和硬件定时器的系统注册入口是分开的,个人并不太为何这么操作,从代码模块化的角度上考虑,软件定时器的实现放置于硬件定时器实现更好。
void rt_system_timer_thread_init(void) { #ifdef RT_USING_TIMER_SOFT int i; for (i = 0; i < sizeof(_soft_timer_list) / sizeof(_soft_timer_list[0]); i++) { //初始化软件定时器列表 rt_list_init(_soft_timer_list + i); } // 软件定时器自旋锁初始化 rt_spin_lock_init(&_stimer_lock); // 信号量初始化(基本可以确定,_timer_thread_entry会通过信号量快速调度了) rt_sem_init(&_soft_timer_sem, "stimer", 0, RT_IPC_FLAG_PRIO); /* 创建并启用定时器,定时周期为10ms,定时器优先级需要通过Kconfig配置*/ rt_thread_init(&_timer_thread, "timer", _timer_thread_entry, RT_NULL, &_timer_thread_stack[0], sizeof(_timer_thread_stack), RT_TIMER_THREAD_PRIO, 10); rt_thread_startup(&_timer_thread); #endif /* RT_USING_TIMER_SOFT */ } int rtthread_startup(void) { // 省略部分 ... /* 系统硬件定时器初始化入口 */ rt_system_timer_init(); //省略部分 ... /* 软件定时器入口 */ rt_system_timer_thread_init(); // 省略部分 ... return 0; }
软件定时器执行入口
// 获取下一个定时器的超时时间 static rt_err_t _timer_list_next_timeout(rt_list_t timer_list[], rt_tick_t *timeout_tick) { struct rt_timer *timer; if (!rt_list_isempty(&timer_list[RT_TIMER_SKIP_LIST_LEVEL - 1])) { timer = rt_list_entry(timer_list[RT_TIMER_SKIP_LIST_LEVEL - 1].next, struct rt_timer, row[RT_TIMER_SKIP_LIST_LEVEL - 1]); *timeout_tick = timer->timeout_tick; return RT_EOK; } return -RT_ERROR; } static void _timer_thread_entry(void *parameter) { rt_err_t ret = RT_ERROR; rt_tick_t next_timeout; rt_base_t level; RT_UNUSED(parameter); rt_sem_control(&_soft_timer_sem, RT_IPC_CMD_SET_VLIMIT, (void*)1); while (1) { /* 获取定时器列表中的下次定时器超时时间 */ level = rt_spin_lock_irqsave(&_stimer_lock); ret = _timer_list_next_timeout(_soft_timer_list, &next_timeout); rt_spin_unlock_irqrestore(&_stimer_lock, level); if (ret != RT_EOK) { // 如果定时器列表为空,则永远等待信号量,降低系统调度压力 rt_sem_take(&_soft_timer_sem, RT_WAITING_FOREVER); } else { rt_tick_t current_tick; /* 获取当前时间 */ current_tick = rt_tick_get(); if ((next_timeout - current_tick) < RT_TICK_MAX / 2) { /* 如果超时时间大于当前时间,则延时对应时间再去调度 由于延时无法退出,因此采用信号量的方式确保有新定时器插入时, 可以及时退出并进行新的定时器检测周期 */ next_timeout = next_timeout - current_tick; rt_sem_take(&_soft_timer_sem, next_timeout); } } // 检查软件定时器列表并执行定时器功能 _timer_check(_soft_timer_list, &_stimer_lock); } }
总结
至此,系统层的软件定时器实现也分析完毕了。我们可以看到,软件定时器的实现,本质上是基于线程,系统创建了一个软件定时器处理线程去处理软件定时器任务。而若不改系统代码,会发现软件定时器的精度更加低,但他也有明显的优势,由于定时器超时执行是在系统调度中完成,因此不存在中断嵌套问题,对系统调度的影响最小。因此,若定时器任务实时性要求不高的话,个人建议软件架构设计中尽量使用软件定时器实现来实现所需功能。