这些小活动你都参加了吗?快来围观一下吧!>>
电子产品世界 » 论坛首页 » 嵌入式开发 » 软件与操作系统 » rtthread系统提供的基于调度定时器的定时器框架分析

共1条 1/1 1 跳转至

rtthread系统提供的基于调度定时器的定时器框架分析

助工
2024-12-16 22:55:25     打赏

       前面已经分析了适配驱动的定时器的实现,而其实RTT的系统源码中,还提供了一套定时器的实现逻辑。由于RTT需要基于硬件定时器提供的滴答时钟来做系统调度,因此理论上这个滴答时钟也可在一定程度上用于用户层面的定时器任务操作。

RTT系统提供的硬件定时器分析

用户层面的硬件定时器注册入口

上层为指针

//对象参数初始化
static void _timer_init(rt_timer_t timer,
                        void (*timeout)(void *parameter),
                        void      *parameter,
                        rt_tick_t  time,
                        rt_uint8_t flag)
{
    int i;

    /* 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_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;

    /* parameter check */
    RT_ASSERT(timeout != RT_NULL);
    RT_ASSERT(time < RT_TICK_MAX / 2);

    /* 生成一个定时器类型的对象 */
    timer = (struct rt_timer *)rt_object_allocate(RT_Object_Class_Timer, name);
    if (timer == RT_NULL)
    {
        return RT_NULL;
    }

    _timer_init(timer, timeout, parameter, time, flag);

    return timer;
}

上层为具体对象

// 上层是具体对象,初始化则简单多了,直接把对象内部的参数初始化即可
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)
{
    /* parameter check */
    RT_ASSERT(timer != RT_NULL);
    RT_ASSERT(timeout != RT_NULL);
    RT_ASSERT(time < RT_TICK_MAX / 2);

    /* 把对象注册成定时器类型 */
    rt_object_init(&(timer->parent), RT_Object_Class_Timer, name);

    /* 初始化定时器参数 */
    _timer_init(timer, timeout, parameter, time, flag);
}
RTM_EXPORT(rt_timer_init);

定时器启用入口

static rt_err_t _timer_start(rt_list_t *timer_list, rt_timer_t timer)
{
    unsigned int row_lvl;
    rt_list_t *row_head[RT_TIMER_SKIP_LIST_LEVEL];
    unsigned int tst_nr;
    static unsigned int random_nr;

    // 定时器已经在队列中,直接返回
    if (timer->parent.flag & RT_TIMER_FLAG_PROCESSING)
    {
        return -RT_ERROR;
    }

    /* 把当前定时器从启用列表中移除 */
    _timer_remove(timer);
    /* 定时器状态切换为未启用 */
    timer->parent.flag &= ~RT_TIMER_FLAG_ACTIVATED;

    RT_OBJECT_HOOK_CALL(rt_object_take_hook, (&(timer->parent)));

    /* 设置定时器的超时时间 */
    timer->timeout_tick = rt_tick_get() + timer->init_tick;

    row_head[0]  = &timer_list[0];
    for (row_lvl = 0; row_lvl < RT_TIMER_SKIP_LIST_LEVEL; row_lvl++)
    {
        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;
            }
            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;
    }

    /* 号称可以提高定时器命中率操作的资源初始化,具体实现只能后续专门分析了 */
    random_nr++;
    tst_nr = random_nr;

    // 将定时器插入定时器列表中
    rt_list_insert_after(row_head[RT_TIMER_SKIP_LIST_LEVEL - 1],
                         &(timer->row[RT_TIMER_SKIP_LIST_LEVEL - 1]));

    // 提供定时器命中率操作的实现
    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;

    return RT_EOK;
}

rt_err_t rt_timer_start(rt_timer_t timer)
{
    rt_sched_lock_level_t slvl;
    int is_thread_timer = 0;
    struct rt_spinlock *spinlock;
    rt_list_t *timer_list;
    rt_base_t level;
    rt_err_t err;

    /* parameter check */
    RT_ASSERT(timer != RT_NULL);
    RT_ASSERT(rt_object_get_type(&timer->parent) == RT_Object_Class_Timer);

// RT_USING_TIMER_SOFT为软件定时器实现部分,晚点专门分析
#ifdef RT_USING_TIMER_SOFT
    if (timer->parent.flag & RT_TIMER_FLAG_SOFT_TIMER)
    {
        timer_list = _soft_timer_list;
        spinlock = &_stimer_lock;
    }
    else
#endif /* RT_USING_TIMER_SOFT */
    {
        timer_list = _timer_list;
        spinlock = &_htimer_lock;
    }

    if (timer->parent.flag & RT_TIMER_FLAG_THREAD_TIMER)
    { // 24年新加的线程定时器入口,具体功能需专门分析
        rt_thread_t thread;
        is_thread_timer = 1;
        rt_sched_lock(&slvl);

        thread = rt_container_of(timer, struct rt_thread, thread_timer);
        RT_ASSERT(rt_object_get_type(&thread->parent) == RT_Object_Class_Thread);
        rt_sched_thread_timer_start(thread);
    }

    level = rt_spin_lock_irqsave(spinlock);

    // 启用定时器
    err = _timer_start(timer_list, timer);

#ifdef RT_USING_TIMER_SOFT
    if (err == RT_EOK && (timer->parent.flag & RT_TIMER_FLAG_SOFT_TIMER))
    {
        rt_sem_release(&_soft_timer_sem);
    }
#endif /* RT_USING_TIMER_SOFT */

    rt_spin_unlock_irqrestore(spinlock, level);

    if (is_thread_timer)
    {
        rt_sched_unlock(slvl);
    }

    return err;
}

     如果清除 RT_USING_TIMER_SOFT 包裹的内容和 RT_TIMER_FLAG_THREAD_TIMER的代码,会发现,启用定时器真正执行功能的入口是_timer_start。而_timer_start中,主要实现的功能是按超时时间将定时器插入现有定时器队列,另外,存在一个号称可以提高定时器命中率的随即操作,但为何可以提高,只能后面针对性分析了。

定时器停用入口

// 把当前定时器从定时器聊表中移除
rt_inline void _timer_remove(rt_timer_t timer)
{
    int i;

    for (i = 0; i < RT_TIMER_SKIP_LIST_LEVEL; i++)
    {
        rt_list_remove(&timer->row[i]);
    }
}

rt_err_t rt_timer_stop(rt_timer_t timer)
{
    rt_base_t level;
    struct rt_spinlock *spinlock;

    /* 检测是否异常 */
    RT_ASSERT(timer != RT_NULL);
    RT_ASSERT(rt_object_get_type(&timer->parent) == RT_Object_Class_Timer);

    spinlock = _timerlock_idx(timer);

    level = rt_spin_lock_irqsave(spinlock);

    if (!(timer->parent.flag & RT_TIMER_FLAG_ACTIVATED))
    {
        rt_spin_unlock_irqrestore(spinlock, level);
        return -RT_ERROR;
    }
    RT_OBJECT_HOOK_CALL(rt_object_put_hook, (&(timer->parent)));

    // 将定时器从运行列表中移除
    _timer_remove(timer);
    /* 清除运行标记 */
    timer->parent.flag &= ~RT_TIMER_FLAG_ACTIVATED;

    rt_spin_unlock_irqrestore(spinlock, level);

    return RT_EOK;
}

     在没有更多信息的情况下,个人认为停用接口并不是最优的实现,很明显,完全可以先判断定时器是否启用再去决定是否执行加锁操作。也许这只能查找历史提交记录才能弄明白是否可以优化了。

定时器断开入口

rt_err_t rt_timer_detach(rt_timer_t timer)
{
    rt_base_t level;
    struct rt_spinlock *spinlock;

    /* parameter check */
    RT_ASSERT(timer != RT_NULL);
    RT_ASSERT(rt_object_get_type(&timer->parent) == RT_Object_Class_Timer);
    RT_ASSERT(rt_object_is_systemobject(&timer->parent));

    spinlock = _timerlock_idx(timer);
    level = rt_spin_lock_irqsave(spinlock);

    // 停用定时器
    _timer_remove(timer);
    timer->parent.flag &= ~RT_TIMER_FLAG_ACTIVATED;

    rt_spin_unlock_irqrestore(spinlock, level);
    // 将定时器从系统的定时器列表中断开
    rt_object_detach(&(timer->parent));

    return RT_EOK;
}

定时器删除入口

rt_err_t rt_timer_delete(rt_timer_t timer)
{
    rt_base_t level;
    struct rt_spinlock *spinlock;

    /* parameter check */
    RT_ASSERT(timer != RT_NULL);
    RT_ASSERT(rt_object_get_type(&timer->parent) == RT_Object_Class_Timer);
    RT_ASSERT(rt_object_is_systemobject(&timer->parent) == RT_FALSE);

    spinlock = _timerlock_idx(timer);

    level = rt_spin_lock_irqsave(spinlock);

    //停用定时器
    _timer_remove(timer);
    timer->parent.flag &= ~RT_TIMER_FLAG_ACTIVATED;
    rt_spin_unlock_irqrestore(spinlock, level);
    
    // 销毁定时器资源
    rt_object_delete(&(timer->parent));

    return RT_EOK;
}
RTM_EXPORT(rt_timer_delete);

定时器控制入口

rt_err_t rt_timer_control(rt_timer_t timer, int cmd, void *arg)
{
    struct rt_spinlock *spinlock;
    rt_base_t level;

    /* parameter check */
    RT_ASSERT(timer != RT_NULL);
    RT_ASSERT(rt_object_get_type(&timer->parent) == RT_Object_Class_Timer);

    spinlock = _timerlock_idx(timer);

    level = rt_spin_lock_irqsave(spinlock);
    switch (cmd)
    {
    case RT_TIMER_CTRL_GET_TIME: // 获取定时器的超时时间
        *(rt_tick_t *)arg = timer->init_tick;
        break;

    case RT_TIMER_CTRL_SET_TIME: // 设置定时器的超时时间,若定时器已运行,则先停用定时器
        RT_ASSERT((*(rt_tick_t *)arg) < RT_TICK_MAX / 2);
        if (timer->parent.flag & RT_TIMER_FLAG_ACTIVATED)
        {
            _timer_remove(timer);
            timer->parent.flag &= ~RT_TIMER_FLAG_ACTIVATED;
        }
        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;

    case RT_TIMER_CTRL_GET_STATE: // 获取定时器运行状态
        if(timer->parent.flag & RT_TIMER_FLAG_ACTIVATED)
        {
            /*timer is start and run*/
            *(rt_uint32_t *)arg = RT_TIMER_FLAG_ACTIVATED;
        }
        else
        {
            /*timer is stop*/
            *(rt_uint32_t *)arg = RT_TIMER_FLAG_DEACTIVATED;
        }
        break;

    case RT_TIMER_CTRL_GET_REMAIN_TIME: // 获取定时器的剩余时间
        *(rt_tick_t *)arg =  timer->timeout_tick;
        break;
    case RT_TIMER_CTRL_GET_FUNC: // 获取定时器的超时处理函数
        *(void **)arg = (void *)timer->timeout_func;
        break;

    case RT_TIMER_CTRL_SET_FUNC: // 设置定时器的超时处理函数
        timer->timeout_func = (void (*)(void*))arg;
        break;

    case RT_TIMER_CTRL_GET_PARM: // 获取定时器回调的传入参数
        *(void **)arg = timer->parameter;
        break;

    case RT_TIMER_CTRL_SET_PARM: // 设置定时器回调的传入参数
        timer->parameter = arg;
        break;

    default:
        break;
    }
    rt_spin_unlock_irqrestore(spinlock, level);

    return RT_EOK;
}

定时器系统初始化入口

// 创建双向队列,队列的prev和next都指向自己,说明两个队列都为空
static rt_list_t _timer_list[RT_TIMER_SKIP_LIST_LEVEL];
static struct rt_spinlock _htimer_lock;

void rt_system_timer_init(void)
{
    rt_size_t i;

    for (i = 0; i < sizeof(_timer_list) / sizeof(_timer_list[0]); i++)
    {  // 初始化定时器列表
        rt_list_init(_timer_list + i);
    }
    // 初始化定时器的自旋锁
    rt_spin_lock_init(&_htimer_lock);
}

// 相当于单片机或者应用程序的main入口
int rtthread_startup(void)
{
    //其他资源初始化
    ...

    /* 系统定时器资源初始化 */
    rt_system_timer_init();

    // 其他资源初始化
    ...
    
    /* 进入系统调度 */
    rt_system_scheduler_start();

    /* never reach here */
    return 0;
}

系统调度入口

     以瑞萨平台为例,系统提供的硬件定时器的调用逻辑如下:

void rt_timer_check(void)
{
    // 省略部分为多核场景下的处理
    ...
    
    // 硬件定时器计时到处理入口
    _timer_check(_timer_list, &_htimer_lock);
}

void rt_tick_increase(void)
{
    // 省略部分为系统其他功能所需入口
    ...
    
    // 系统提供的硬件定时器处理入口
    rt_timer_check();
}

// SysTick_Handler放置于中断向量表中
void SysTick_Handler(void)
{
    /* enter interrupt */
    rt_interrupt_enter();

#ifdef SOC_SERIES_R9A07G0
    __set_CNTP_CVAL(__get_CNTP_CVAL() + rtt_timer_delay);
#endif

    // 系统滴答处理入口
    rt_tick_increase();

    /* leave interrupt */
    rt_interrupt_leave();
}

定时器调度实现

static void _timer_check(rt_list_t *timer_list, struct rt_spinlock *lock)
{
    struct rt_timer *t;
    rt_tick_t current_tick;
    rt_base_t level;
    rt_list_t list;

    level = rt_spin_lock_irqsave(lock);

    current_tick = rt_tick_get();

    rt_list_init(&list);

    while (!rt_list_isempty(&timer_list[RT_TIMER_SKIP_LIST_LEVEL - 1]))
    {
        t = rt_list_entry(timer_list[RT_TIMER_SKIP_LIST_LEVEL - 1].next,
                          struct rt_timer, row[RT_TIMER_SKIP_LIST_LEVEL - 1]);

        /* 重新获取时间(软件定时器回调执行时间可能超过一个tick,若不重新获取,可能会异常) */
        current_tick = rt_tick_get();

        if ((current_tick - t->timeout_tick) < RT_TICK_MAX / 2)
        { // 如果当前系统时间比定时器的超时时间大,则认为定时器已经超时,需要执行超时操作
            RT_OBJECT_HOOK_CALL(rt_timer_enter_hook, (t));

            /* 将定时器从定时器运行列表中移除,若为单次执行定时器,则清空运行中标志位 */
            _timer_remove(t);
            if (!(t->parent.flag & RT_TIMER_FLAG_PERIODIC))
            {
                t->parent.flag &= ~RT_TIMER_FLAG_ACTIVATED;
            }

            // 设置定时器执行中标志位,以防其他操作占用
            t->parent.flag |= RT_TIMER_FLAG_PROCESSING;
            /* 将定时器列表 t->row[RT_TIMER_SKIP_LIST_LEVEL - 1] 插入到临时队列  */
            rt_list_insert_after(&list, &(t->row[RT_TIMER_SKIP_LIST_LEVEL - 1]));

            rt_spin_unlock_irqrestore(lock, level);

            /* call timeout function */
            t->timeout_func(t->parameter);

            RT_OBJECT_HOOK_CALL(rt_timer_exit_hook, (t));

            level = rt_spin_lock_irqsave(lock);

            // 清除定时器执行中标记
            t->parent.flag &= ~RT_TIMER_FLAG_PROCESSING;

            /* 若临时队列已经清空,则继续执行下一次查找 */
            if (rt_list_isempty(&list))
            {
                continue;
            }
            // 从临时队列中清除定时器t->row[RT_TIMER_SKIP_LIST_LEVEL - 1]
            rt_list_remove(&(t->row[RT_TIMER_SKIP_LIST_LEVEL - 1]));
            if ((t->parent.flag & RT_TIMER_FLAG_PERIODIC) &&
                (t->parent.flag & RT_TIMER_FLAG_ACTIVATED))
            {/* 如果是周期运行且为运行中的定时器,则重新装载定时器 */
                t->parent.flag &= ~RT_TIMER_FLAG_ACTIVATED;
                _timer_start(timer_list, t);
            }
        }
        else break;
    }
    rt_spin_unlock_irqrestore(lock, level);
}

     由于定时器在链表中的顺序是按超时时间去排布的,因此直接break并不会导致有到超时时间的定时器无法执行的问题。只是,不详细分析RT_TIMER_SKIP_LIST_LEVEL的实现,还是不明白为何随机能提高命令率。而瑞萨的代码,RT_TIMER_SKIP_LIST_LEVEL这个宏的值默认为1,也就是说,随机操作的基础并未执行,因此这部分等后续遇到真正的随机操作执行时再去详细理解。

总结

    至此,系统提供的硬件定时器实现已经分析完毕,而在分析过程中,我们会发现,此定时器接口与系统调度是同一优先级的实现,因此在使用此定时器时,要严格注意定时器的内部实现尽可能地短,以免影响系统调度的及时性。另外,此定时器的执行最小时间间隔为系统的滴答周期,因此只能执行时间精度要求不高的定时器任务,高精度的定时器任务,还是得直接调用硬件定时器框架去实现。






关键词: rtthread     系统     基于     定时器     调度     分析    

共1条 1/1 1 跳转至

回复

匿名不能发帖!请先 [ 登陆 注册 ]