将从初始化、应用、相关代码与系统的关联三个方面分析代码。
初始化
初始化顺序执行,先进行了链表的清空,在componets.c文件中
rtthread_startup()->rt_system_timer_init()
void rt_system_timer_init(void)
{
int i;
for (i = 0; i < sizeof(rt_timer_list) / sizeof(rt_timer_list[0]); i++)
{
rt_list_init(rt_timer_list + i);
}
}
其中rt_timer_list被定义在timer.c中,该是一个链表指针,其中RT_TIMER_SKIP_LIST_LEVEL被定义为1,表示跳表层数为1,具体跳表算法见官方文档主要目的是用来提高查表效率。
static rt_list_t rt_timer_list[RT_TIMER_SKIP_LIST_LEVEL];
初始化函数实际就是让链表节点指针的头尾都指向自己,也就是清空链表。
下一个初始化函数通用在rtthread_startup()函数中
void rt_system_timer_thread_init(void)
{
#ifdef RT_USING_TIMER_SOFT
int i;
for (i = 0;
i < sizeof(rt_soft_timer_list) / sizeof(rt_soft_timer_list[0]);
i++)
{
rt_list_init(rt_soft_timer_list + i);
}
/* start software timer thread */
rt_thread_init(&timer_thread,
"timer",
rt_thread_timer_entry,
RT_NULL,
&timer_thread_stack[0],
sizeof(timer_thread_stack),
RT_TIMER_THREAD_PRIO,
10);
/* startup */
rt_thread_startup(&timer_thread);
#endif
}
由宏RT_USING_TIMER_SOFT便知,他决定该函数是否被启动。这里说明一下
HARD_TIMER和模式SOFT_TIMER,前者是让定时器超时发送在中断中,后者是让超时发生在进程中。要达到后面的效果那肯定要建立一个新的进程了。
这里先通上文初始化一样,将rt_soft_timer_list也清空了下,然后通过静态方式来创建线程
线程池:
static rt_uint8_t timer_thread_stack[RT_TIMER_THREAD_STACK_SIZE];
大小512,线程优先级为0,入口函数为rt_thread_timer_entry。
static void rt_thread_timer_entry(void *parameter)
{
rt_tick_t next_timeout;
while (1)
{
/* get the next timeout tick */
next_timeout = rt_timer_list_next_timeout(rt_soft_timer_list);
if (next_timeout == RT_TICK_MAX)
{
/* no software timer exist, suspend self. */
rt_thread_suspend(rt_thread_self());
rt_schedule();
}
else
{
rt_tick_t current_tick;
/* get current tick */
current_tick = rt_tick_get();
if ((next_timeout - current_tick) < RT_TICK_MAX / 2)
{
/* get the delta timeout tick */
next_timeout = next_timeout - current_tick;
rt_thread_delay(next_timeout);
}
}
/* check software timer */
rt_soft_timer_check();
}
}
该进程是一个死循环,循环开始读取距离最近的超时定时器定时,这里再补充一下RTT的定时器链表排序,它是根据超时时间大小进行了一个排序,例如我们先后定义了在tick 20,100,50后超时的三个定时器,当前那个全局tick累加到了10,那么被塞入链表的三个定时器tick超时值配排序为: 30 60 110。系统只需要匹配当前tick是否和链表最近一个tick值相同,便能知道是否超时了,详情通用见官方文档。
if (next_timeout == RT_TICK_MAX)该句在判断链表中是否有定时器,如果没有,则该线程放弃当前的线程控制权。它是处于0优先级的线程,他不放弃则下面的线程都无法执行了。这里用的是suspend,也就是挂起了该线程,其他地方不打开它,则线程轮询是不会再执行它了的。
在else里面就是读取当前系统tick然后比对最近的一个定时有无超时,如果没有超时,则计算出还需要多少次tick才能超时,直接。。。进行延时了! - -|
到这里就剩最后一个函数了rt_soft_timer_check()。该函数其实就在rt_thread_timer_entry的上方,稍微长了些,就不贴源码了。
该函数中主要就是判断链表里面的定时器有没有超时,当然又出现了下列句式
if ((current_tick - t->timeout_tick) < RT_TICK_MAX / 2)
这里专门说明一下,由于系统tick在计数满了之后会归零继续计数的,而且这里根本没有负数的概念。举个例子,想要得到目标是否超时,应该写成: 目标时间 - 设定时间 >=0 ,虽然tick是无符号,但是计算的时候也可以看成有符号的。数据对半砍,前段是正后段是负而已。那么 >=0 就等效于 < RT_TICK_MAX / 2 了,正数是取数据的前半段嘛,那么就组装成上面的不等式了。
这里如果被判断超时,则先执行挂载在rt_timer_enter_hook上的回调函数,然后从链表中删去该定时器,之后调用绑定的定时器中断函数,最后调用绑定在rt_timer_exit_hook上的回调函数。完成后判断一下这个定时器是否是一个连续定时器,如果是,则再次打开定时器。
至此关于定时器随系统初始化部分就结束了,其实没使能RT_USING_TIMER_SOFT宏的话,也就是仅仅情况了一下链表而已。
应用
这里不是讲怎么用定时器,是从使用的方向来分析代码
动态定时器创建函数rt_timer_create()
rt_timer_t rt_timer_create(const char *name,
void (*timeout)(void *parameter),
void *parameter,
rt_tick_t time,
rt_uint8_t flag)
{
struct rt_timer *timer;
/* allocate a object */
timer = (struct rt_timer *)rt_object_allocate(RT_Object_Class_Timer, name);
if (timer == RT_NULL)
{
return RT_NULL;
}
_rt_timer_init(timer, timeout, parameter, time, flag);
return timer;
}
其中flag参数在rtdef.h中被定义
#define RT_TIMER_FLAG_ONE_SHOT 0x0 /* 单次定时 */ #define RT_TIMER_FLAG_PERIODIC 0x2 /* 周期定时 */ #define RT_TIMER_FLAG_HARD_TIMER 0x0 /* 硬件定时器 */ #define RT_TIMER_FLAG_SOFT_TIMER 0x4 /* 软件定时器 */
使用逻辑或可以并列选择,其实看宏具体值就知道,内部是判断标志位,如果对应位置没有找到1,就使用默认的单次硬件定时器。
然后回到create函数,这里使用了rt_object_allocate方式分配除了一个timer的内存空间,具体早hope分析的时候详细说明函数实现,这里就知道它动态分配了个timer结构体大小的空间出来即可。
然后对该结构体赋初值_rt_timer_init(),该函数中并未将定时器放入链表,仅仅是初始化了timer结构体的数据
/* set flag */
timer->parent.flag = flag;
/* set deactivated */
timer->parent.flag &= ~RT_TIMER_FLAG_ACTIVATED;
timer->timeout_func = timeout;
timer->parameter = parameter;
timer->timeout_tick = 0;
timer->init_tick = time;
/* initialize timer list */
for (i = 0; i < RT_TIMER_SKIP_LIST_LEVEL; i++)
{
rt_list_init(&(timer->row[i]));
}
接下来分析定时器的启动函数rt_timer_start(),因为太长,不贴取全部代码。请自己脑补
函数第一步就是删除了传入的timer定时器
_rt_timer_remove(timer);
该函数是将timer的row参数从它所处于的链表中剔除。调用了rt_object_take_hook的回调函数。
之后通过当前时间+超时时间来计算出超时的绝对时间,这个时间就是用来比对tick判断超时的量
timer->timeout_tick = rt_tick_get() + timer->init_tick;
接着判断是使用中断方式还是线程方式,两种分别使用了不同的链表
#ifdef RT_USING_TIMER_SOFT
if (timer->parent.flag & RT_TIMER_FLAG_SOFT_TIMER)
{
/* insert timer to soft timer list */
timer_list = rt_soft_timer_list;
}
else
#endif
{
/* insert timer to system timer list */
timer_list = rt_timer_list;
}
接下来是一大堆插表操作,使用了跳表的算法,整个表是按照超时时间顺序排列,具体未研究,之后再单独来看。好,我们就当它正确的插入了定时链表中。
最后又看到了宏RT_USING_TIMER_SOFT
#ifdef RT_USING_TIMER_SOFT
if (timer->parent.flag & RT_TIMER_FLAG_SOFT_TIMER)
{
/* check whether timer thread is ready */
if ((timer_thread.stat & RT_THREAD_STAT_MASK) == RT_THREAD_SUSPEND)
{
/* resume timer thread to check soft timer */
rt_thread_resume(&timer_thread);
rt_schedule();
}
}
#endif
这里就和之前的定时器线程挂起进程成对儿了,创建一个新的定时器将会重新就绪定时器线程。
如果使用的是中断方式,则就要跑远一点,在clock.c中的rt_tick_increase函数最后有函数rt_timer_check();,先补充一下rt_tick_increase函数,在系统时钟的文章中也有说明,他是在系统时钟发生中断时被调用的,也就是该函数中执行的功能,实际上也是发送在中断里的。
rt_timer_check()函数和rt_soft_timer_check()函数差不多,也不赘述了,判断链表里的定时器是否超时,超时则执行回调。
其他貌似没啥还需要分析的了,因为经常存改变定时器设定值的情况,所以最后单独说下配置函数rt_timer_control
rt_err_t rt_timer_control(rt_timer_t timer, int cmd, void *arg)
{
/* timer check */
RT_ASSERT(timer != RT_NULL);
RT_ASSERT(rt_object_get_type(&timer->parent) == RT_Object_Class_Timer);
switch (cmd)
{
case RT_TIMER_CTRL_GET_TIME:
*(rt_tick_t *)arg = timer->init_tick;
break;
case RT_TIMER_CTRL_SET_TIME:
timer->init_tick = *(rt_tick_t *)arg;
break;
case RT_TIMER_CTRL_SET_ONESHOT:
timer->parent.flag &= ~RT_TIMER_FLAG_PERIODIC;
break;
case RT_TIMER_CTRL_SET_PERIODIC:
timer->parent.flag |= RT_TIMER_FLAG_PERIODIC;
break;
}
return RT_EOK;
}
注意的是该句
timer->init_tick = *(rt_tick_t *)arg;
他是修改了相对超时时间,由上文可知,判断超时是按照计算后的绝对时间,所以想不对修改该值让超时延迟是不可行的。在使用完成该函数修改相对超时时间后,还需要使用start函数来重新计算新的绝对超时时间。
至此代码分析结束
和系统的纠葛
如果使用线程方式,也就是定义了宏RT_USING_TIMER_SOFT,那么肯定会用到系统的线程相关函数,中断方式则没有。
如果使用动态定时器创建,则需要系统的堆被初始化过,也就是在drv_common.c中rt_hw_board_init函数的
#if defined(RT_USING_HEAP)
rt_system_heap_init((void *)HEAP_BEGIN, (void *)HEAP_END);
#endif
静态创建则没有该要求。
再来就是时钟源,定时器和系统时钟是牢牢的绑在一起的。首先来看线程方式的定时器,它使用了rt_thread_delay函数来等待第一个定时器的超时。这里不得不补充说明一下rt_thread_delay函数了,实际上它是启动了每个线程都配备的一个定时器,然后挂起了线程,定时器超时后再恢复。这里的定时器当然只能用中断方式的。也就是线程方式的定时器里面用了个中断方式的定时器,这弄这么麻烦也是想将超时回调函数执行在线程中。
那么整个定时器的超时判断变汇聚到一点,也就是在clock.c中rt_tick_increase函数里的绝对时间变量rt_tick 和 rt_timer_check()函数 上了。如果想分离定时器和系统时钟,也是从这里入手。
网友评论