美文网首页
RTT笔记-定时器相关代码分析

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

作者: lissettecarlr | 来源:发表于2021-02-14 19:09 被阅读0次

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

    初始化

    初始化顺序执行,先进行了链表的清空,在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笔记-定时器相关代码分析

          本文链接:https://www.haomeiwen.com/subject/zywjqctx.html