当前位置: 首页 > news >正文

Rt-thread源码剖析(2)——时钟与定时器

前言

        时钟是所有系统工作的心脏,单片机有自己的主频,每一步逻辑算术运算都发生在时钟信号的翻转时刻;RTOS自然也有自己的时钟系统,本文着眼于RTT的系统时钟以及定时器两个方向对RTT的内核进行阐述

系统时钟

        RTOS的时钟是线程运行的最小时间片,也是系统调度发生的最小单位,接下来我们依次来看OS tick从哪来,怎么起作用

系统时钟的源头

        在rtt_port.c内,MCU厂商会在此通过自己的硬件时钟(mchtimer)设置一个周期性的中断,在这个硬件中断中,调用 rt_tick_increase()接口,实现tick的自增

        

        需要注意的是,RT_TICK_PER_SECOND是定义在rtconfig.h中的一个常量,因此,在这个硬件中断中,你需要根据自身MCU的主频来设置相应的中断触发间隔。

时钟增长做了什么

        在硬件时钟的中断中,除了将下一次中断触发的时间设置成一个预期的时间点,其他只调用了rt_tick_increase();我们看一下他做了什么

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

    RT_OBJECT_HOOK_CALL(rt_tick_hook, ());

    level = rt_hw_interrupt_disable();

    /* increase the global tick */
#ifdef RT_USING_SMP
    rt_cpu_self()->tick ++;
#else
    ++ rt_tick;
#endif /* RT_USING_SMP */

    /* check time slice */
    thread = rt_thread_self();

    -- thread->remaining_tick;
    if (thread->remaining_tick == 0)
    {
        /* change to initialized tick */
        thread->remaining_tick = thread->init_tick;
        thread->stat |= RT_THREAD_STAT_YIELD;

        rt_hw_interrupt_enable(level);
        rt_schedule();
    }
    else
    {
        rt_hw_interrupt_enable(level);
    }

    /* check timer */
    rt_timer_check();
}

概括来说,当OS tick增长时,会依次进行以下操作:

        1.如果你注册了tick hook,那么会自动执行

        2.禁用全局中断

        3.全局变量rt_tick自增

        4.获取当前运行线程的句柄,将其剩余时间减少一个tick

        5.如果当前运行线程剩余时间为0,则打开全局中断,进行一次线程调度

        6.打开全局中断

        7.检查是否有定时器超时

关于线程调度的详细内容,会展其他文章内进行展开,接下来着重讲一下第7点,与定时器有关的内容

定时器

定时器结构体

        首先我们来看下timer struct的源码,结构体的成员就是创建时需要传入的参数

struct rt_timer
{
    struct rt_object parent;                            /**< inherit from rt_object */

    rt_list_t        row[RT_TIMER_SKIP_LIST_LEVEL];

    void (*timeout_func)(void *parameter);              /**< timeout function */
    void            *parameter;                         /**< timeout function's parameter */

    rt_tick_t        init_tick;                         /**< timer timeout tick */
    rt_tick_t        timeout_tick;                      /**< timeout tick */
};
typedef struct rt_timer *rt_timer_t;

        首先的第一个成员是rt_object parent,该成员就是本系列第一篇文章中提到的内核成员的公共部分,在此不再展开

        其次需要传入超时回调函数,回调函数所需的参数以及超时时间

这里展开一下row[RT_TIMER_SKIP_LIST_LEVEL];

        定时器在创建成功后进入定时队列时,是进入一个按照剩余时间排序的链表,而该链表是支持调表的。因此,如果你将RT_TIMER_SKIP_LIST_LEVEL定义为1,那么就意味着不使用跳表,定义为n,就代表有n层跳表

定时器创建

        定时器的创建分为动态和静态,这一部分是和内核容器对象完全一致的,动态和静态创建最终调用的都是_rt_timer_init

static void _rt_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]));
    }
}

这里值得说的有以下两点:

1.定时器在创建后的标志位是deactivated的,需要start后才会被激活

2.定时器的timeout_tick初始化成了0,也就是如果你设置了500个tick后超时,是从start开始计时的,而非创建后

定时器启动

定时器启动的源码非常多,因此先贴上代码大概做了什么的流程,然后再附上源码

rt_timer_start 其主要功能包括:

  1. 参数检查:确认传入的定时器指针 timer 非空,并且其对象类型为 RT_Object_Class_Timer

  2. 停止定时器:在启动前,先停止定时器,也就是说调用两次接口会重置超时时间

  3. 计算超时时间:设置定时器的超时时刻 timeout_tick 为当前系统时钟加上定时器的初始计时时间 init_tick

  4. 选择定时器列表:根据定时器类型(硬件或软件),选择相应的定时器列表,下文会展开

  5. 插入定时器:将定时器按照超时时间插入到定时器列表的适当位置,按超时时间排序

  6. 激活定时器:标记定时器为激活状态,并在必要时唤醒软件定时器处理线程。

    rt_err_t rt_timer_start(rt_timer_t timer)
    {
        unsigned int row_lvl;
        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;
    
        /* timer check */
        RT_ASSERT(timer != RT_NULL);
        RT_ASSERT(rt_object_get_type(&timer->parent) == RT_Object_Class_Timer);
    
        /* stop timer firstly */
        level = rt_hw_interrupt_disable();
        /* remove timer from list */
        _rt_timer_remove(timer);
        /* change status of timer */
        timer->parent.flag &= ~RT_TIMER_FLAG_ACTIVATED;
    
        RT_OBJECT_HOOK_CALL(rt_object_take_hook, (&(timer->parent)));
    
        /*
         * get timeout tick,
         * the max timeout tick shall not great than RT_TICK_MAX/2
         */
        RT_ASSERT(timer->init_tick < RT_TICK_MAX / 2);
        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;
        }
    
        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;
    
                /* fix up the entry pointer */
                t = rt_list_entry(p, struct rt_timer, row[row_lvl]);
    
                /* If we have two timers that timeout at the same time, it's
                 * preferred that the timer inserted early get called early.
                 * So insert the new timer to the end the the some-timeout timer
                 * list.
                 */
                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;
        }
    
        /* 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;
    
        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;
            /* Shift over the bits we have tested. Works well with 1 bit and 2
             * bits. */
            tst_nr >>= (RT_TIMER_SKIP_LIST_MASK + 1) >> 1;
        }
    
        timer->parent.flag |= RT_TIMER_FLAG_ACTIVATED;
    
        /* enable interrupt */
        rt_hw_interrupt_enable(level);
    
    #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);
                rt_schedule();
            }
        }
    #endif
    
        return RT_EOK;
    }

硬件定时和软件定时

        这里首先要明确的是,这个概念基于定时器超时的回调函数运行环境来区分的,在OS tick自增的中断中会调用rt_timer_check();该运行环境处于中断,因此如果是此时运行的定时器,成为硬件定时器;

        而如果开启了软件定时的宏,则会在系统启动过程中,开启一个timer_thread,他的优先级由RT_TIMER_THREAD_PRIO定义,一般需要选择一个较高的优先级,确保定时器超时后能第一时间执行,这样的定时器则成为软件定时器。

        rt_soft_timer_checkrt_timer_check逻辑几乎一致,软件定时多了一个线程更加复杂,因此以软件定时举例

软件定时线程入口

        软件定时器的线程入口函数如下:

/* system timer thread 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();
    }
}

         首先,在线程运行时,如果定时器队列为空,则会将自己挂起并释放CPU;如果定时器队列非空,则会看一下当前队列中下一个超时的定时器还有多久,比如说还有100个tick,则他将自己挂起100个tick,最后进入定时器处理

        这里不必担心如果这100个tick内如果有新创建的定时器,如果超时时间更短会来不及调度,因为timer在start的时候,会唤醒timer_thread

timer_check

rt_soft_timer_check函数用于检查并处理软件定时器的超时事件。以下是该函数的主要步骤:

  1. 初始化临时列表:创建并初始化一个临时列表list,用于暂存已超时的定时器,这个列表是用于存放当前正在被处理的定时器的,如果在定时器超时函数执行完毕后,该列表为空,证明在其他线程里已经删除了这个定时器,即使它是周期定时器,也不应该再次启动

  2. 禁用中断:调用rt_hw_interrupt_disable函数,禁用中断以确保在操作定时器列表时的原子性。

  3. 遍历定时器列表:循环检查软件定时器跳表的最高层级(即rt_soft_timer_list[RT_TIMER_SKIP_LIST_LEVEL - 1])中的定时器。

    • 获取当前时间:调用rt_tick_get函数,获取当前的系统节拍计数current_tick

    • 判断定时器是否超时:比较当前时间current_tick与定时器的超时时间t->timeout_tick。如果当前时间未达到超时时间,退出循环。

    • 处理超时定时器:对于已超时的定时器,执行以下操作:

      • 调用钩子函数:如果定义了rt_timer_enter_hook,则调用该钩子函数,表示定时器即将触发。

      • 从定时器列表中移除:调用_rt_timer_remove函数,将定时器从定时器列表中移除。

      • 检查定时器类型:如果定时器是非周期性的(即单次定时器),清除其激活标志RT_TIMER_FLAG_ACTIVATED

      • 加入临时列表:将该定时器插入到临时列表list中,以便稍后处理。

      • 设置定时器状态:将软定时器状态soft_timer_status设置为RT_SOFT_TIMER_BUSY,表示定时器正在处理。

      • 启用中断:在调用定时器的超时回调函数之前,先启用中断,以允许其他中断或任务的执行。

      • 调用超时回调函数:执行定时器的超时回调函数t->timeout_func,并传递参数t->parameter

      • 调用退出钩子函数:如果定义了rt_timer_exit_hook,则在回调函数执行完毕后调用,表示定时器处理完成。

      • 禁用中断:回调函数执行完毕后,重新禁用中断,以继续安全地操作定时器列表。

      • 重置定时器状态:将软定时器状态soft_timer_status设置为RT_SOFT_TIMER_IDLE,表示定时器处理空闲。

      • 检查定时器状态:如果定时器未被重新启动且为非周期性定时器,则从临时列表中移除。

      • 重新启动周期性定时器:如果定时器是周期性的且仍处于激活状态,调用rt_timer_start函数重新启动定时器。

  4. 启用中断:在处理完所有超时定时器后,调用rt_hw_interrupt_enable函数,重新启用中断

细节:

1.遍历timer链表时,需关闭全局中断;但进行定时器超时回调函数时,允许中断

2.在进行定时器超时回调时,该定时器已经有可能被其他线程删除了,所以需要临时列表来暂存他

源码如下

void rt_soft_timer_check(void)
{
    rt_tick_t current_tick;
    struct rt_timer *t;
    register rt_base_t level;
    rt_list_t list;

    rt_list_init(&list);

    RT_DEBUG_LOG(RT_DEBUG_TIMER, ("software timer check enter\n"));

    /* disable interrupt */
    level = rt_hw_interrupt_disable();

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

        current_tick = rt_tick_get();

        /*
         * It supposes that the new tick shall less than the half duration of
         * tick max.
         */
        if ((current_tick - t->timeout_tick) < RT_TICK_MAX / 2)
        {
            RT_OBJECT_HOOK_CALL(rt_timer_enter_hook, (t));

            /* remove timer from timer list firstly */
            _rt_timer_remove(t);
            if (!(t->parent.flag & RT_TIMER_FLAG_PERIODIC))
            {
                t->parent.flag &= ~RT_TIMER_FLAG_ACTIVATED;
            }
            /* add timer to temporary list  */
            rt_list_insert_after(&list, &(t->row[RT_TIMER_SKIP_LIST_LEVEL - 1]));

            soft_timer_status = RT_SOFT_TIMER_BUSY;
            /* enable interrupt */
            rt_hw_interrupt_enable(level);

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

            RT_OBJECT_HOOK_CALL(rt_timer_exit_hook, (t));
            RT_DEBUG_LOG(RT_DEBUG_TIMER, ("current tick: %d\n", current_tick));

            /* disable interrupt */
            level = rt_hw_interrupt_disable();

            soft_timer_status = RT_SOFT_TIMER_IDLE;
            /* Check whether the timer object is detached or started again */
            if (rt_list_isempty(&list))
            {
                continue;
            }
            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))
            {
                /* start it */
                t->parent.flag &= ~RT_TIMER_FLAG_ACTIVATED;
                rt_timer_start(t);
            }
        }
        else break; /* not check anymore */
    }
    /* enable interrupt */
    rt_hw_interrupt_enable(level);

    RT_DEBUG_LOG(RT_DEBUG_TIMER, ("software timer check leave\n"));
}

相关文章:

  • CAN总线通信协议学习4——数据链路层之仲裁规则
  • DHCP配置实验
  • 企业建设——控制措施类型
  • 静态时序分中的case analysis传播分析
  • 迷你世界脚本世界UI接口:UI
  • react 编写一个待办事项,函数优化,组件传值
  • openssl下aes128算法gcm模式加解密运算实例
  • MyBatis-Plus 元对象处理器 @TableField注解 反射动态赋值 实现字段自动填充
  • logback日志输出配置范例
  • 基于第三方SDK的Windows平台全功能RTMP|RTSP直播播放器深度解析
  • C++20 中的 `consteval` 和 `constinit` 特性
  • Bash Shell 比较注入漏洞:分析与利用
  • 深入解析:域名转换成 IP 地址的多种方式
  • Element Plus使用(五)
  • Java 设计模式:软件开发的精髓与艺
  • 机器学习工程师技术图谱和学习路线
  • C++特殊类设计
  • 18、深拷贝与浅拷贝的区别【中高频】
  • 基于springboot+vue的线上考试系统的设计与实现
  • 使用Java构建高效的Web服务架构
  • 网站建设教程所需文字/境外电商有哪些平台
  • 北京网站制作公司哪家好/快手刷评论推广网站
  • 网站建设软件开发工作室整站模板/百度网盘人工客服电话
  • 专门做优选的网站/网站快速优化排名
  • 怎么样让网站网址有图标/深圳seo专家
  • 东莞最新通报最新/seo关键词优化方法