RT-Thread分析-时钟节拍和定时器管理

目录

1 前言

2 时钟节拍

2.1 RT_TICK_PER_SECOND

2.2 rt_tick

2.3 SysTick_Handler

1)rt_tick_increase

2.4 rt_tick溢出问题

3 定时器实现

3.1 HARD_TIMER/SOFT_TIMER

3.2 工作机制

3.3 跳表算法

1)RT_TIMER_SKIP_LIST_LEVEL

3.4 结构体定义

4 定时器接口分析

4.1 定时器创建

1) _timer_init()

4.2 定时器启动

4.3 定时器停止

4.4 定时器删除

4.5 定时器脱离

5 定时器超时处理

5.1 HARD_TIMER定时器

1) rt_timer_check

5.2 SOFT_TIMER定时器

1) _timer_thread_entry


1 前言

2 时钟节拍

        时钟节拍用于系统处理所有和时间有关的事件,如线程的延时、线程的时间片轮转调度以及定时器超时等,可以理解为内核的心跳。

2.1 RT_TICK_PER_SECOND

        时钟节拍都由一个固定的硬件定时器来实现,该定时器设定固定的超时时间,一般为1~100ms。时钟节拍率越快,系统的额外开销就越大,但系统的响应速度会更快。

        该时间可 以 根 据 RT_TICK_PER_SECOND 的 定 义 来 调 整, 等 于 1/ RT_TICK_PER_SECOND 秒。

  • 可以通过menuconfig设置RT_TICK_PER_SECOND

RT-Thread分析-时钟节拍和定时器管理_第1张图片

  • 或者直接修改rtconfig.h中的RT_TICK_PER_SECOND定义
#define RT_TICK_PER_SECOND 1000

2.2 rt_tick

  • 内核通过一个全局变量rt_tick来记录时钟节拍,在每次时钟节拍中断中都会自加一次。
static volatile rt_tick_t rt_tick = 0;
  • 内核提供获取时钟节拍的接口rt_tick_get(void),用于内核一些超时处理的判定。
rt_tick_t rt_tick_get(void)
{
    /* return the global tick */
    return rt_tick;
}

2.3 SysTick_Handler

        时钟节拍中断回调函数,本质就是某个硬件定时器的中断回调;每个硬件平台移植RTthread时都需要移植该函数,下面以stm32平台分析。主要工作如下:

  • 全局变量时钟节拍计数器rt_tick自加1
  • 检查当前线程时间片是否耗尽,如果耗尽则先去做线程调度操作
  • 检查定时器链表并做相应处理
void SysTick_Handler(void)
{
    /* enter interrupt */
    rt_interrupt_enter();

    //只用于stm32,该平台驱动库会使uwTick来做一些驱动超时处理
    if(SysTick->CTRL & SysTick_CTRL_COUNTFLAG_Msk)
        HAL_IncTick();

    //时钟节拍具体处理函数
    rt_tick_increase();

    /* leave interrupt */
    rt_interrupt_leave();
}

1)rt_tick_increase

void rt_tick_increase(void)
{
    struct rt_thread *thread;
    rt_base_t level;

    level = rt_hw_interrupt_disable();

    //1. rt_tick自加,分多核和单核场景 */
#ifdef RT_USING_SMP
    rt_cpu_self()->tick ++;
#else
    ++ rt_tick;
#endif /* RT_USING_SMP */

    //2. 检查当前线程时间片
    thread = rt_thread_self();
    -- thread->remaining_tick;
    if (thread->remaining_tick == 0)
    {
        //2.1 当前线程时间片超时,修改线程状态为RT_THREAD_STAT_YIELD
        thread->remaining_tick = thread->init_tick;
        thread->stat |= RT_THREAD_STAT_YIELD;

        //2.2 执行线程调度
        rt_hw_interrupt_enable(level);
        rt_schedule();
    }
    else
    {
        rt_hw_interrupt_enable(level);
    }

    //3 检查定时器
    rt_timer_check();
}

2.4 rt_tick溢出问题

        内核使用全局变量rt_tick_t rt_tick记录节拍,对于32位系统,rt_tick最大为0xFFFFFFFF。按10ms一个节拍计算,大概497天时rt_tick就会溢出。在定时器使用逻辑中,一般定时器启动时设定超时时间为next_time,满足current_time >= next_time就认为定时器超时,但如果在溢出前后,就会出现错误判断。

  • 内核是如此解决的
//等效于无论是否回绕都满足current_tick >= t->timeout_tick
if ((current_tick - t->timeout_tick) < RT_TICK_MAX / 2)

        PS:该方式有个限制,就是定时器超时时间设定必须小于RT_TICK_MAX/2,否则上述判断就会出现错误。

3 定时器实现

        内核提供了完善的软件定时器功能,以时钟节拍(OS Tick)的时间长度为单位,即定时器的超时时间必须是一个节拍的整数倍。

3.1 HARD_TIMER/SOFT_TIMER

  • 内核依据定时器超时函数执行位置,将分定时器分为HARD_TIMER和SOFT_TIMER模式。可以在创建定时器时使用参数选择。
  • HARD_TIMER模式下,超时函数直接在中断上下文执行,对于超时函数的要求与中断服务例程的要求相同:执行时间应该尽量短,执行时不应导致当前上下文挂起、等待。
  • SOFT_TIMER模式需要先开启设置才能使用,内核会单独开启一个线程_timer_thread,将SOFT_TIMER模式的超时函数都放到该线程执行。

RT-Thread分析-时钟节拍和定时器管理_第2张图片

3.2 工作机制

  • 所有定时器都是基于rt_tick来记录经历的时间。rt_tick固定每个时钟节拍自加+1
  • 内核维护两个定时器链表:_soft_timer_list和_timer_list。新创建的定时器都按超时时间排序的方式插入到定时器链表。HARD_TIMER/SOFT_TIMER模式的定时器统一放到链表_timer_list/_soft_timer_list管理。
  • 例如当前rt_tick为20,此时创建并启动了三个定时器,分别是定时时间为50 个 tick 的 Timer1、100 个 tick 的 Timer2 和 500 个 tick 的 Timer3,这三个定时器分别加上系统当前时间 rt_tick=20,从小到大排序链接在 rt_timer_list 链表中,形成下图的定时器链表结构。

RT-Thread分析-时钟节拍和定时器管理_第3张图片

  • 每次时钟节拍中断中,rt_tick会自加1,并和链表中的定时器超时时间进行比较,如果rt_tick>=timeout则说明该定时器超时,则处理对应的回调函数

3.3 跳表算法

        前面已经分析过,定时器在启动时会按超时时间排序插入到链表中,如果从头开始遍历链表,效率很低时间复杂度O(n)。当使用定时器数量比较大时,每次新创建一个定时器并启动时,会有很大的开销。内核选择使用跳表算法来加速遍历速度。

  • 跳表是一种基于并联链表的数据结构,利用空间换取时间的办法,可以实现二分查找的有序链表,插入、删除、查找的时间复杂度均为 O(log n)
  • 跳表如何实现

1)原始有序单链表如下

2)从链表中每两个元素抽出来,加一级索引,一级索引指向了原始链表

RT-Thread分析-时钟节拍和定时器管理_第4张图片

3)同样查找10,原始链表需要遍历1,3,4,5,7,8,9->10;而跳表中只需要先遍历一级索引1,4,7,9->10

1)RT_TIMER_SKIP_LIST_LEVEL

        在 RT-Thread 中通过宏定义 RT_TIMER_SKIP_LIST_LEVEL 来配置跳表的层数,默认为 1,默认不使用跳表。

3.4 结构体定义

struct rt_timer
{
    struct rt_object parent;                       //内核对象

    rt_list_t        row[RT_TIMER_SKIP_LIST_LEVEL];//链表节点

    void (*timeout_func)(void *parameter);        //定时器超时函数
    void            *parameter;                   //定时器超时函数参数 

    rt_tick_t        init_tick;                   //定时器时的超时周期  
    rt_tick_t        timeout_tick;                //创建时 超时周期+当前系统tick
};
typedef struct rt_timer *rt_timer_t;

4 定时器接口分析

4.1 定时器创建

  • 分为动态创建rt_timer_create()和静态创建rt_timer_init();区别在于rt_timer结构体在内核动态创建还是外部静态声明
rt_timer_t rt_timer_create(const char *name,//定时器名称
                           void (*timeout)(void *parameter),//定时器超时函数
                           void       *parameter,//定时器超时函数参数
                           rt_tick_t   time,//定时器定时时间间隔
                           rt_uint8_t  flag)//定时器内核对象标志

void rt_timer_init(rt_timer_t  timer,//定时器句柄
                   const char *name,//定时器名称
                   void (*timeout)(void *parameter),//定时器超时函数
                   void       *parameter,//定时器超时函数参数
                   rt_tick_t   time,//定时器定时时间间隔
                   rt_uint8_t  flag)//定时器内核对象标志
  • 创建时,参数重点需要关注flag,b[1]置1表示周期定时器,置0表示单次定时器;b[2]置1表示SOFT_TIMER,置0表示HARD_TIMER;相互之间可以使用“或”同时赋值flag
#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     /* 软 件 定 时 器 */

       

         PS:需要注意的是,如果使用SOFT_TIMER,需要内核配置先打开RT_USING_TIMER_SOFT,否则即使flag使能0x4,但还是会按HARD_TIMER处理,代码使用宏RT_USING_TIMER_SOFT来屏蔽SOFT_TIMER功能

1) _timer_init()

        rt_timer_create()和rt_timer_init()最终会调用_timer_init()完成创建,在该函数中对定时器结构体数据进行初始化

4.2 定时器启动

rt_err_t rt_timer_start(rt_timer_t timer)
{
    //1 关闭中断
    level = rt_hw_interrupt_disable();
    
    //2 如果定时器在定时器链表中,先移除
    _timer_remove(timer);
    
    //3 将定时器超时周期+当前系统tick  
    RT_ASSERT(timer->init_tick < RT_TICK_MAX / 2); //超时时间最大不能超过RT_TICK_MAX/2,这个已经在rt_tick溢出问题中分析过
    timer->timeout_tick = rt_tick_get() + timer->init_tick;
    
    //4 通过定时器类型决定使用哪个定时器链表来管理
#ifdef RT_USING_TIMER_SOFT
    if (timer->parent.flag & RT_TIMER_FLAG_SOFT_TIMER)
    {
        /* insert timer to soft timer list */
        timer_list = _soft_timer_list;
    }
    else
#endif /* RT_USING_TIMER_SOFT */
    {
        /* insert timer to system timer list */
        timer_list = _timer_list;
    }
    
    //5 赋值定时器链表,从链表头开始
    row_head[0]  = &timer_list[0];
    
    //RT_TIMER_SKIP_LIST_LEVEL默认1,则只执行一次,不做跳表算法
    for (row_lvl = 0; row_lvl < RT_TIMER_SKIP_LIST_LEVEL; row_lvl++)  
    {
        //6 从链表头开始依次遍历定时器链表,如果为空则直接跳过
        for (; row_head[row_lvl] != timer_list[row_lvl].prev;
             row_head[row_lvl]  = row_head[row_lvl]->next)
        {
            struct rt_timer *t;
            //6.1 获取下一个链表节点
            rt_list_t *p = row_head[row_lvl]->next;

            //6.2 依据链表节点获取定时器结构体
            t = rt_list_entry(p, struct rt_timer, row[row_lvl]);

            //6.3 如果超时时间相同,则谁先启动谁就排序在前面
            if ((t->timeout_tick - timer->timeout_tick) == 0)
            {
                continue;
            }
            //6.3 当下一个链表节点对应的定时器,其超时时间大于需启动timer时,表示已找到需插入的位置
            else if ((t->timeout_tick - timer->timeout_tick) < RT_TICK_MAX / 2)
            {
                break;
            }
        }
        
        //暂不理会,用于跳表算法
        if (row_lvl != RT_TIMER_SKIP_LIST_LEVEL - 1)
            row_head[row_lvl + 1] = row_head[row_lvl] + 1;
    }
    
    //7 将需启动的定时器timer插入到步骤6中遍历的节点后
    rt_list_insert_after(row_head[RT_TIMER_SKIP_LIST_LEVEL - 1],
                         &(timer->row[RT_TIMER_SKIP_LIST_LEVEL - 1]));
                         
    //8 设置定时器状态
    timer->parent.flag |= RT_TIMER_FLAG_ACTIVATED;
    
    //9 如果启动定时器是SOFT_TIMER则唤醒_timer_thread线程去处理
#ifdef RT_USING_TIMER_SOFT
    if (timer->parent.flag & RT_TIMER_FLAG_SOFT_TIMER)
    {
        /* check whether timer thread is ready */
        if ((_soft_timer_status == RT_SOFT_TIMER_IDLE) &&
           ((_timer_thread.stat & RT_THREAD_STAT_MASK) == RT_THREAD_SUSPEND))
        {
            /* resume timer thread to check soft timer */
            rt_thread_resume(&_timer_thread);
            need_schedule = RT_TRUE;
        }
    }
#endif /* RT_USING_TIMER_SOFT */
    
    rt_hw_interrupt_enable(level);

    if (need_schedule)
    {
        rt_schedule();
    }
}
  • 在整个定时器启动过程中是一直关闭中断的rt_hw_interrupt_disable()
  • 依据当前系统rt_tick和定时器超时周期,得出定时器基于系统tick的超时时间赋值给timer->timeout_tick
  • 依据定时器类型SOFT_TIMER/HARD_TIMER,选择对应的定时器链表
  • 从定时器链表表头开始,依次遍历,依据超时时间按从小到大的顺序,将定时器插入链表中(超时时间一样的定时器,谁先启动的谁排序在前面)
  • 如果是SOFT_TIMER,则唤醒_timer_thread线程,并执行任务调度

4.3 定时器停止

rt_err_t rt_timer_stop(rt_timer_t timer)
{
    //1. 关闭中断
    level = rt_hw_interrupt_disable();

    //2. 将定时器从链表中移除,并且修改定时器状态
    _timer_remove(timer);
    timer->parent.flag &= ~RT_TIMER_FLAG_ACTIVATED;

    //3. 打开中断
    rt_hw_interrupt_enable(level);

    return RT_EOK;
}
  • 函数执行过程中是屏蔽中断的
  • 将定时器从链表中移除,并修改状态为未激活

4.4 定时器删除

rt_err_t rt_timer_delete(rt_timer_t timer)
{
    //1. 关闭中断
    level = rt_hw_interrupt_disable();

    //2. 将定时器从管理链表中移除,并修改状态为未激活
    _timer_remove(timer);
    timer->parent.flag &= ~RT_TIMER_FLAG_ACTIVATED;

    //3. 打开中断
    rt_hw_interrupt_enable(level);

    //4. 将定时器对象从内核对象容器中脱离,并释放对象所占用的内存
    rt_object_delete(&(timer->parent));

    return RT_EOK;
}

4.5 定时器脱离

        rt_err_t rt_timer_detach(rt_timer_t timer)

  • 相比定时器删除,唯一的区别就是不释放对象所占用的内存

5 定时器超时处理

5.1 HARD_TIMER定时器

        HARD_TIMER定时器是在rt_timer_check()函数中进行超时处理的,该函数在每次时钟节拍中断SysTick_Handler中执行

1) rt_timer_check

void rt_timer_check(void)
{
    //1 初始化一个临时列表
    rt_list_init(&list);
    
    //2 获取当前系统节拍
    current_tick = rt_tick_get();
    
    //3 关闭中断
    level = rt_hw_interrupt_disable();
    
    //4 从链表头开始遍历,处理定时器超时事件
    while (!rt_list_isempty(&_timer_list[RT_TIMER_SKIP_LIST_LEVEL - 1]))
    {
        //4.1 获取链表节点对应的定时器结构体
        t = rt_list_entry(_timer_list[RT_TIMER_SKIP_LIST_LEVEL - 1].next,
                          struct rt_timer, row[RT_TIMER_SKIP_LIST_LEVEL - 1]); 
        
        //4.2 如果当前系统时间大于定时器超时时间,则认为定时器超时并进行相应处理
        if ((current_tick - t->timeout_tick) < RT_TICK_MAX / 2)
        {
            //4.2.1 暂时将该定时器从管理链表中移除
            _timer_remove(t);
            
            //4.2.2 将该定时器插入到临时链表list中,作用后面会体现
            rt_list_insert_after(&list, &(t->row[RT_TIMER_SKIP_LIST_LEVEL - 1]));
            
            //4.2.3 执行超时回调
            t->timeout_func(t->parameter);
            
            //4.2.4 再次获取系统时间(上面回调函数是对外给用户,时间开销可能会很大)
            current_tick = rt_tick_get();
            
            //4.2.5 结合步骤4.2.2,如果此时定时器不在临时链表list中,说明用户在超时回调中有调用start或者detach,
            //那么该定时器也就无需后续处理,可以直接遍历下一个定时器
            if (rt_list_isempty(&list))
            {
                continue;
            }
            
            //4.2.6 将定时器从临时链表list中移除
            rt_list_remove(&(t->row[RT_TIMER_SKIP_LIST_LEVEL - 1]));
            
            //4.2.7 如果是周期定时器,则再次启动
            if ((t->parent.flag & RT_TIMER_FLAG_PERIODIC) &&
                (t->parent.flag & RT_TIMER_FLAG_ACTIVATED))
            {
                /* start it */
                t->parent.flag &= ~RT_TIMER_FLAG_ACTIVATED;
                rt_timer_start(t);
            }
        }
        //4.3 否则则直接退出遍历,因为链表是按超时时间从小到大顺序排列的,只要当前系统时间小于该节点超时时间,后续节点都不需要再比较了                      
        else break;                                                                                                                                                                                                                               
                                                                  
    }
    
    //5 打开中断
    rt_hw_interrupt_enable(level);
}
  • HARD_TIMER定时器的超时回调是在时钟节拍中断中执行,需严格满足中断上下文执行要求
  • 整个过程处于中断关闭状态
  • 从定时器管理链表_timer_list头开始遍历,通过当前系统时间大于节点定时器超时时间,则执行回调函数;并对周期性定时器进行再启动处理

5.2 SOFT_TIMER定时器

        SOFT_TIMER定时器是在线程_timer_thread_entry中做超时处理,该线程优先级是由RT_TIMER_THREAD_PRIO决定,堆栈大小是由RT_TIMER_THREAD_STACK_SIZE决定。该线程会在内核启动时调用rt_system_timer_thread_init()完成初始化

void rt_system_timer_thread_init(void)
{
#ifdef RT_USING_TIMER_SOFT
    //1 初始化软件定时器管理链表
    for (i = 0;i < sizeof(_soft_timer_list) / sizeof(_soft_timer_list[0]);i++)
    {
        rt_list_init(_soft_timer_list + i);
    }

    //2 初始化线程并启动
    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 */
}

1) _timer_thread_entry

static void _timer_thread_entry(void *parameter)
{
    while (1)
    {
        //1 获取软件定时器链表第一个节点的超时时间(定时器是按超时时间从小到大的顺序排列)
        next_timeout = _timer_list_next_timeout(_soft_timer_list);
        
        //2 链表为空时,_timer_list_next_timeout返回RT_TICK_MAX
        if (next_timeout == RT_TICK_MAX)
        {
            //2.1 说明无软件定时器需管理,则该线程主动挂起;当软件定时器启动时,会唤醒再次该线程
            rt_thread_suspend(rt_thread_self());
            rt_schedule();
        }
        else
        {
            //3 获取定时器超时时间大于当前系统时间,则计算差值next_timeout,并让该线程休眠next_timeout个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);
            }
        }
        
        //4 这里就不做分析,步骤和rt_timer_check()类似,只是定时器链表换成_soft_timer_list
        rt_soft_timer_check();
    }
}

你可能感兴趣的:(RT-Thread,os,嵌入式)