前面分析完系统提供的硬件定时器入口时发现有宏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);
}
}总结
至此,系统层的软件定时器实现也分析完毕了。我们可以看到,软件定时器的实现,本质上是基于线程,系统创建了一个软件定时器处理线程去处理软件定时器任务。而若不改系统代码,会发现软件定时器的精度更加低,但他也有明显的优势,由于定时器超时执行是在系统调度中完成,因此不存在中断嵌套问题,对系统调度的影响最小。因此,若定时器任务实时性要求不高的话,个人建议软件架构设计中尽量使用软件定时器实现来实现所需功能。
我要赚赏金
