RT-Thread内核实现(五):定时器

整体思想
  • 前几章的线程延时是通过线程的remaining_tick属性来记录延时时间,在 SysTick 中断中扫描并更新每个线程remaining_tick,如果remaining_tick等于0,将该线程就绪,时间复杂度O(n);而定时器的策略是,每个线程都有自己的定时器,当线程需要延时时,先将线程挂起,然后启动内置的定时器,并将定时器挂到一个系统维护的全局的定时器列表rt_timer_list,每个节点代表了正在延时的线程的定时器,节点按照延时时间大小做升序排列,所以时基更新时只用扫描系统定时器列表的第一个节点timeout_tick,如果第一个定时器未到期,则后面的也不用判断,时间复杂度O(1)。定时器结构体如下,
    struct rt_timer
    {
        struct rt_object parent;                         /* 从 rt_object 继承 */
    
        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;                   /* 定时器实际超时时的系统节拍数 = rt_tick + init_tick */
    };
    typedef struct rt_timer *rt_timer_t;
    
  • 结构体成员 rt_list_t row[RT_TIMER_SKIP_LIST_LEVEL]; 是数据结构跳跃表(skip list)的应用,一种以空间换时间的查找策略,后面有详细点的说明。
  • 结构体成员 void (*timeout_func)(void *parameter);定时器超时后调用该函数;
  • 结构体成员 rt_tick_t timeout_tick; 其值timeout_tick = rt_tick + init_tickrt_tick是该定时器启动时系统的tick数值,加上init_tick,表示定时器超时时,系统tick的数值。
  • 使用定时器要先初始化系统定时器列表。rt_system_timer_init();
  • 定时器初始化函数rt_timer_init(),在线程初始化时调用。
/**
 * 该函数用于初始化一个定时器,通常该函数用于初始化一个静态的定时器
 *
 * @param timer 静态定时器对象
 * @param name 定时器的名字
 * @param timeout 超时函数
 * @param parameter 超时函数形参
 * @param time 定时器的超时时间
 * @param 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)
{
    /* 定时器对象初始化 */
    rt_object_init((rt_object_t)timer, RT_Object_Class_Timer, name);

    /* 定时器初始化 */
    _rt_timer_init(timer, timeout, parameter, time, flag);
}
启动定时器
  • rt_err_t rt_timer_start(rt_timer_t timer); 这里有个宏RT_TIMER_SKIP_LIST_LEVEL,表示定时器跳跃表等级,默认是1。这个函数会更新该定时器标志parent.flag和超时时间timeout_tick,然后在系统定时器列表中找到合适的位置插入该定时器节点。
/**
 * 启动定时器,并将定时器挂到定时器列表
 *
 * @param timer 将要启动的定时器
 *
 * @return 操作状态, RT_EOK on OK, -RT_ERROR on error
 */
rt_err_t rt_timer_start(rt_timer_t timer)
{
    unsigned int row_lvl = 0;
    rt_list_t *timer_list;
    register rt_base_t level;
    rt_list_t *row_head[RT_TIMER_SKIP_LIST_LEVEL];
    unsigned int tst_nr;
    static unsigned int random_nr;


    /* 关中断 */
    level = rt_hw_interrupt_disable();
    
    /* 将定时器从系统定时器列表移除 */
    _rt_timer_remove(timer);
    
    /* 改变定时器的状态为非active */
    timer->parent.flag &= ~RT_TIMER_FLAG_ACTIVATED;
    
    /* 开中断 */
    rt_hw_interrupt_enable(level);

    /* 获取 timeout tick,
       最大的timeout tick 不能大于 RT_TICK_MAX/2 */
    timer->timeout_tick = rt_tick_get() + timer->init_tick;

    /* 关中断 */
    level = rt_hw_interrupt_disable();


    /* 将定时器插入到定时器列表 */
    /* 获取系统定时器列表根节点地址,rt_timer_list是一个全局变量 */
    timer_list = rt_timer_list;


    /* 获取系统定时器列表第一条链表根节点地址 */
    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++)
    {
        /* 列表不为空,当没有定时器被插入到系统定时器列表时,该循环不执行 */
        /* RT_TIMER_SKIP_LIST_LEVEL = 1, 则row_lvl始终等于0
         * 当 row_head[0]->timeout_tick < timer->timeout_tick 时更新row_head[0]的值,即向后排
         */ 
        for (; row_head[row_lvl] != timer_list[row_lvl].prev; row_head[row_lvl]  = row_head[row_lvl]->next)
        {
            struct rt_timer *t;
            
            /* 获取定时器列表节点地址 */
            rt_list_t *p = row_head[row_lvl]->next;

            /* 根据节点地址获取父结构的指针 */
            t = rt_list_entry(p,                 /* 节点地址 */
                              struct rt_timer,   /* 节点所在父结构的数据类型 */
                              row[row_lvl]);     /* 节点在父结构中叫什么,即名字 */
            
            /* 两个定时器的超时时间相同,则继续在定时器列表中寻找下一个节点 */
            if ((t->timeout_tick - timer->timeout_tick) == 0)
            {
                continue;
            }
            /* uint 相减 */
            else if ((t->timeout_tick - timer->timeout_tick) < RT_TICK_MAX / 2)
            {
                break;
            }
            
        }
        /* RT_TIMER_SKIP_LIST_LEVEL等于1时,条件不会成真,不会被执行 */
        /* 如果RT_TIMER_SKIP_LIST_LEVEL不等于1,这里更新到下一层节点,继续向后找 */
        if (row_lvl != RT_TIMER_SKIP_LIST_LEVEL - 1)
        {
           row_head[row_lvl + 1] = row_head[row_lvl] + 1;	// ------------------------------ ⑴
        }            
    }

    /* random_nr是一个静态变量,用于记录启动了多少个定时器 */
    /* Interestingly, this super simple timer insert counter works very very
     * well on distributing the list height uniformly. By means of "very very
     * well", I mean it beats the randomness of timer->timeout_tick very easily
     * (actually, the timeout_tick is not random and easy to be attacked). */
    random_nr++;
    tst_nr = random_nr;

    /* 将定时器插入到系统定时器列表 */
    /* 假如只有一个定时器列表,row_head[0]表示上面找到的合适的插入位置 */
    rt_list_insert_after(row_head[RT_TIMER_SKIP_LIST_LEVEL - 1],       /* 双向列表根节点地址 */
                         &(timer->row[RT_TIMER_SKIP_LIST_LEVEL - 1])); /* 要被插入的节点的地址 */ // ---------- ⑵
    
    /* RT_TIMER_SKIP_LIST_LEVEL 等于1,该for循环永远不会执行 */
    /* 这里是为了跳跃表的随机插入 */
    for (row_lvl = 2; row_lvl <= RT_TIMER_SKIP_LIST_LEVEL; row_lvl++) 
    {
        if (!(tst_nr & RT_TIMER_SKIP_LIST_MASK))
            rt_list_insert_after(row_head[RT_TIMER_SKIP_LIST_LEVEL - row_lvl],
                                 &(timer->row[RT_TIMER_SKIP_LIST_LEVEL - row_lvl]));	// ----------------------------- ⑶
        else
            break;

        tst_nr >>= (RT_TIMER_SKIP_LIST_MASK + 1) >> 1;
    }

    /* 设置定时器标志位为激活态 */
    timer->parent.flag |= RT_TIMER_FLAG_ACTIVATED;

    /* 开中断 */
    rt_hw_interrupt_enable(level);

    return -RT_EOK;
}
  • 如果RT_TIMER_SKIP_LIST_LEVEL为1,timer_list就是一个普通的、有序的双向链表。
  • 如果RT_TIMER_SKIP_LIST_LEVEL大于1,代码中跳跃表的功能就体现出来了,引用 SkipList的那点事儿 中的一张图。

定时器结构体成员row[RT_TIMER_SKIP_LIST_LEVEL] 表示该节点在跳跃表中的纵向分布,根据跳转表的特性,第一级链表必须包含所有节点,所以row[RT_TIMER_SKIP_LIST_LEVEL - 1]必须挂在rt_timer_list[RT_TIMER_SKIP_LIST_LEVEL - 1]上。

  • 再从上往下看,首先row_head[0]更新为跳跃表最上层的第一个节点,内部的for循环的目的是找到当前这一级合适的节点,然后在第⑴处垂直下降到下一层,继续下一层的搜索。row_head[row_lvl]中存的是每一层所有比目标值小的节点中最大的节点,当外层的for循环退出后,row_head[row_lvl]之后就是整个跳跃表中第一级合适的插入位置,在第⑵处执行插入操作,此时row_lvl = RT_TIMER_SKIP_LIST_LEVEL - 1
  • 第⑶处的代码就是从第一级往上走,随机插入。方便之后的查找。
扫描定时器
  • 扫描定时器是为了找出已经超时了的线程,根据跳转表的特性,只需要扫描跳跃表的最后一层,当current_tick >= t->timeout_tick时,说明该定时器超时了,然后调用调用超时函数,传入的参数是该线程的线程控制块地址,再根据定时器标志停止或重新启动该定时器。
/**
 * 该函数用于扫描系统定时器列表,当有超时事件发生时
 * 就调用对应的超时函数
 *
 * @note 该函数在操作系统定时器中断中被调用
 */
void rt_timer_check(void)
{
    struct rt_timer *t;
    rt_tick_t current_tick;
    register rt_base_t level;

    /* 获取系统时基计数器rt_tick的值 */
    current_tick = rt_tick_get();

    /* 关中断 */
    level = rt_hw_interrupt_disable();

    /* 系统定时器列表不为空,则扫描定时器列表 */
    while (!rt_list_isempty(&rt_timer_list[RT_TIMER_SKIP_LIST_LEVEL - 1]))
    {
        /* 获取第一个节点定时器的地址 */
        t = rt_list_entry(rt_timer_list[RT_TIMER_SKIP_LIST_LEVEL - 1].next,   /* 节点地址 */
                        struct rt_timer,                        /* 节点所在的父结构的数据类型 */ 
                        row[RT_TIMER_SKIP_LIST_LEVEL - 1]);     /* 节点在父结构的成员名 */
        /* uint 如果 current_tick > t->timeout_tick, 说明该定时器超时了 */
        if ((current_tick - t->timeout_tick) < RT_TICK_MAX / 2)
        {
            /* 先将定时器从定时器列表移除 */
            _rt_timer_remove(t);

            /* 调用超时函数 */
            t->timeout_func(t->parameter);

            /* 重新获取 rt_tick */
            current_tick = rt_tick_get();

            /* 如果t是周期定时器,则重新启动 */
            if ((t->parent.flag & RT_TIMER_FLAG_PERIODIC) &&
                (t->parent.flag & RT_TIMER_FLAG_ACTIVATED))
            {
                /* 启动定时器 */
                t->parent.flag &= ~RT_TIMER_FLAG_ACTIVATED;
                rt_timer_start(t);
            }
            /* 单次定时器 */
            else
            {
                /* 停止定时器 */
                t->parent.flag &= ~RT_TIMER_FLAG_ACTIVATED;
            }
        }
        else
            break;
    }

    /* 开中断 */
    rt_hw_interrupt_enable(level);
}
超时函数
/**
 * 线程超时函数
 * 当线程延时到期或者等待的资源可用或者超时时,该函数会被调用
 *
 * @param parameter 超时函数的形参
 */
void rt_thread_timeout(void *parameter)
{
    struct rt_thread *thread;

    thread = (struct rt_thread *)parameter;

    /* 设置错误码为超时 */
    thread->error = -RT_ETIMEOUT;

    /* 将线程从挂起列表中删除,本章节并没有使用挂起列表 */
    rt_list_remove(&(thread->tlist));

    /* 将线程插入到就绪列表 */
    rt_schedule_insert_thread(thread);

    /* 系统调度 */
    rt_schedule();
}
延时函数
  • rt_err_t rt_thread_delay(rt_tick_t tick); 先挂起该线程,然后设置该线程定时器的超时时间,然后启动定时器,执行系统调度。
/* 延时函数 */
rt_err_t rt_thread_delay(rt_tick_t tick)
{
    return rt_thread_sleep(tick);
}

rt_err_t rt_thread_sleep(rt_tick_t tick)
{
    register rt_base_t temp;
    struct rt_thread *thread;
    
    /* 关中断 */
    temp = rt_hw_interrupt_disable();
    
    /* 获取当前线程的线程控制块 */
    thread = rt_current_thread;

    /* 挂起线程 */
    rt_thread_suspend(thread);

    /* 设置线程定时器的超时时间 */
    rt_timer_control(&(thread->thread_timer), RT_TIMER_CTRL_SET_TIME, &tick);
    
    /* 启动定时器 */
    rt_timer_start(&(thread->thread_timer));

    /* 开中断 */
    rt_hw_interrupt_enable(temp);

    /* 执行系统调度 */
    rt_schedule();

    return RT_EOK;
}
时基更新函数
  • 在SysTick中断被调用,函数实体由rt_timer_check()代替。
void rt_tick_increase(void)
{
    rt_tick ++;

	rt_timer_check();
}
工程代码

11_timer

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值