RTT笔记-定时器相关代码分析

将从初始化、应用、相关代码与系统的关联三个方面分析代码。

初始化

初始化顺序执行,先进行了链表的清空,在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()函数 上了。如果想分离定时器和系统时钟,也是从这里入手。

你可能感兴趣的:(RTT笔记-定时器相关代码分析)