zoukankan      html  css  js  c++  java
  • rtos之定时器实现

    在RT-Thread中,每个线程都内置一个定时器,当线程需要延时的时候,先将线程挂起,然后内置的定时器将会启动,并且将定时器插入到一个全局的定时器列表rt_timer_list,这个全局的系统定时器列表维护着一条双向链表,每个节点代表了正在延时的线程的定时器,节点按照延时时间大小做升序排列。当每次时基中断(SysTick中断)来临时,就扫描系统定时器列表的第一个定时器,看看延时是否到,如果到则让该定时器对应的线程就绪,如果延时时间不到,则退出扫描,因为定时器的延时时间是按升序排列的,第一个定时器延时的时间不到的话,那后面的定时器的延时时间自然不到期。

    1、系统定时器列表

    在 RT-Thread 中,定义了一个全局的系统定时器列表,当线程需要延时时,就先把线程挂起,然后线程内置的定时器将线程挂起到这个系统定时器列表中,系统定时器列表维护着一条双向链表,节点按照定时器的延时时间的大小做升序排列。该系统定时器在 timer.c中定义。
    /* 硬件定时器列表 */
     static rt_list_t rt_timer_list[RT_TIMER_SKIP_LIST_LEVEL]; (1)
     
    (1) :系统定时器列表是一个 rt_list 类型的数组,数组的大小由在rtdef.h 中定义的宏 RT_TIMER_SKIP_LIST_LEVEL 决定,默认定义为 1,即数组只有一个成
    员。

    2、系统定时器列表初始化

    系统定时器列表初始化由函数 rt_system_timer_init() 来完成,在初始化调度器前需要先初始化系统定时器列表。该函数在 timer.c 中定义,具体实现:
    void rt_system_timer_init(void)
    { 
      int i;
      for (i = 0; i < sizeof(rt_timer_list) / sizeof(rt_timer_list[0]); i++) (1) 
      { 
        rt_list_init(rt_timer_list + i); (2) 
      } 
    }
     
    (1) :系统定时器列表是一个 rt_list 节点类型的数组,rt_timer_list里面的成员就是一个双向链表的根节点,有多少个成员就初始化多少个根节点,目前只有一个,所以该 for 循环只执行一次。
     
    (2) :初始化节点,即初始化节点的 next 和 prev 这两个指针指向节点本身。初始化好的系统定时器列表的示意图具体见图: 初始化好的系统定时器列表。

     3、定义定时器结构体

    定时器统一由一个定时器结构体来管理,该结构体在 rtdef.h 中定义,具体实现:

    /*
    * 定时器结构体
    */
    struct rt_timer
    { 
      struct rt_object parent; /* 从 rt_object 继承 */ (1) 
      rt_list_t row[RT_TIMER_SKIP_LIST_LEVEL]; /* 节点 */ (2)
      void (*timeout_func)(void *parameter); /* 超时函数 */ (3) 
      void *parameter; /* 超时函数形参 */ (4)
      rt_tick_t init_tick; /* 定时器实际需要延时的时间 */ (5)
      rt_tick_t timeout_tick; /* 定时器实际超时时的系统节拍数 */ (6)
    };
    typedef struct rt_timer *rt_timer_t; (7)
     
    (1) :定时器也属于内核对象,也会在自身结构体里面包含一个内核对象类型的成员,通过这个成员可以将定时器挂到系统对象容器里面。
    (2)定时器自身的节点,通过该节点可以实现将定时器插入到系统定时器列表。RT_TIMER_SKIP_LIST_LEVEL 在 rtdef.h 中定义,默认为 0。
    (3) :定时器超时函数,当定时器延时到期时,会调用相应的超时函数,该函数接下来会讲解。
    (4) :定时器超时函数形参。
    (5) :定时器实际需要延时的时间,单位为 tick。
    (6) :定时器实际超时时的系统节拍数。这个如何理解?我们知道系统定义了一个全局的系统时基计数器 rt_tick(在 clock.c 中定义),每产生一次系统时基中
    断(即 SysTick 中断)时,rt_tick 计数加一。假设线程要延时 10 个 tick,即 init_tick 等于 10,此时 rt_tick 等于 2,那么 timeout_tick 就等于 10 加 2 等于12,当 rt_tick 递增到 12 的时候,线程延时到期,这个就是 timeout_tick 的实际含义。

    4、定时器初始化函数

    定时器初始化函数 rt_timer_init 在 timer.c 中定义,具体实现:
    /**
    * 该函数用于初始化一个定时器,通常该函数用于初始化一个静态的定时器
    * 
    * @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); (1)
    
      /* 定时器初始化 */
       _rt_timer_init(timer, timeout, parameter, time, flag); (2)
    }
    (1) :定时器对象初始化,即将定时器插入到系统对象容器列表。
    (2) :定时器初始化函数 rt_timer_init 将定时器具体的初始化由封装在了一个内部函数 _rt_timer_init(函数开头的“_rt”表示该函数是一个内部函数)中,该函
    数在 timer.c 中定义,具体实现:
    static void _rt_timer_init(rt_timer_t timer, (1) 
                    void (*timeout)(void *parameter), (2) 
                    void *parameter, (3) 
                    rt_tick_t time, (4) 
                    rt_uint8_t flag) (5) 
    { 
      int i;
      /* 设置标志 */
      timer->parent.flag = flag; (6)
    
      /* 先设置为非激活态 */
       timer->parent.flag &= ~RT_TIMER_FLAG_ACTIVATED; (7)
    
      timer->timeout_func = timeout; (8)
      timer->parameter = parameter; (9)
    
      /* 初始化 定时器实际超时时的系统节拍数 */
      timer->timeout_tick = 0; (10)
      /* 初始化 定时器需要超时的节拍数 */
      timer->init_tick = time; (11)
    
      /* 初始化定时器的内置节点 */
      for (i = 0; i < RT_TIMER_SKIP_LIST_LEVEL; i++) (12)
      {
        rt_list_init(&(timer->row[i]));
       }
    }
    (1):定时器控制块指针。
    (2):定时器超时函数。
    (3):定时器超时函数形参。
    (4):定时器实际需要延时的时间。
    (5):设置定时器的标志,取值在 rtdef.h 中定义,具体见:
    #define RT_TIMER_FLAG_DEACTIVATED 0x0 /* 定时器没有激活 */
    #define RT_TIMER_FLAG_ACTIVATED 0x1 /* 定时器已经激活 */
    #define RT_TIMER_FLAG_ONE_SHOT 0x0 /* 单次定时 */
    #define RT_TIMER_FLAG_PERIODIC 0x2 /* 周期定时 */
    #define RT_TIMER_FLAG_HARD_TIMER 0x0 /* 硬件定时器,定时器回调函数在tick isr 中调用 */
    #define RT_TIMER_FLAG_SOFT_TIMER 0x4 /* 软件定时器,定时器回调函数在定时器线程中调用 */
    (6):设置标志。
    (7):初始时设置为非激活态。
    (8):设置超时函数,超时函数接下来会讲。
    (9):定时器超时函数形参。
    (10):初始化定时器实际超时时的系统节拍数。
    (11):初始化定时器需要超时的节拍数。
    (12):初始化定时器的内置节点,即将节点的 next 和 prev 这两个指针指向节点本身。当启动定时器的时候,定时器就通过该节点将自身插入到系统定时器
    列表 rt_timer_list 中。

     5、定时器删除函数

    定时器删除函数 _rt_timer_remove 在 timer.c 中定义,实现算法是将定时器自身的节点从系统定时器列表 rt_timer_list 脱离即可,具体实现:
    rt_inline void _rt_timer_remove(rt_timer_t timer)
     { 
      int i;
      for (i = 0; i < RT_TIMER_SKIP_LIST_LEVEL; i++) 
      { 
        rt_list_remove(&timer->row[i]);
      } 
     }

    6、定时器停止函数

    定时器停止函数 rt_timer_stop 在 timer.c 中定义,实现的算法也很简单,主要分成两步,先将定时器从系统定时器列表删除,然后改变定时器的状态为非 active 即可,具体代码实现:
    /**
    * 该函数将停止一个定时器
    * 
    * @param timer 将要被停止的定时器 
    * 
    * @return 操作状态, RT_EOK on OK, -RT_ERROR on error
    */
    rt_err_t rt_timer_stop(rt_timer_t timer)
    {
         register rt_base_t level;
    
       /* 只有 active 的定时器才能被停止,否则退出返回错误码 */
       if (!(timer->parent.flag & RT_TIMER_FLAG_ACTIVATED))
       return -RT_ERROR;
    
       /* 关中断 */
       level = rt_hw_interrupt_disable();
    
       /* 将定时器从定时器列表删除 */
       _rt_timer_remove(timer);
    
       /* 开中断 */
       rt_hw_interrupt_enable(level);
    
       /* 改变定时器的状态为非 active */
       timer->parent.flag &= ~RT_TIMER_FLAG_ACTIVATED;
    
       return RT_EOK;
     }

    7、定时器控制函数

    定时器控制函数 rt_timer_control 在 timer.c 中定义,具体实现算法是根据不同的形参来设置定时器的状态和初始时间值,具体代码实现:
    /**
    * 该函数将获取或者设置定时器的一些选项
    * 
    * @param timer 将要被设置或者获取的定时器
    * @param cmd 控制命令
    * @param arg 形参
    * 8 * @return RT_EOK
    */                       (1)       (2)        (3)
     rt_err_t rt_timer_control(rt_timer_t timer, int cmd, void *arg)
     {
       switch (cmd)
       {
         case RT_TIMER_CTRL_GET_TIME: (4)
         *(rt_tick_t *)arg = timer->init_tick;
         break;
    
        case RT_TIMER_CTRL_SET_TIME: (5)
         timer->init_tick = *(rt_tick_t *)arg;
        break;
    
        case RT_TIMER_CTRL_SET_ONESHOT:
        timer->parent.flag &= ~RT_TIMER_FLAG_PERIODIC; (6)
        break;
       
    case RT_TIMER_CTRL_SET_PERIODIC:    timer->parent.flag |= RT_TIMER_FLAG_PERIODIC; (7)    break;   } return RT_EOK; }
    (1):timer 表示要控制的定时器。
    (2):cmd 表示控制命令,取值在 rtdef.h 中定义,具体:
    #define RT_TIMER_CTRL_SET_TIME 0x0 /* 设置定时器定时时间 */
    #define RT_TIMER_CTRL_GET_TIME 0x1 /* 获取定时器定时时间 */
    #define RT_TIMER_CTRL_SET_ONESHOT 0x2 /* 修改定时器为一次定时 */
    #define RT_TIMER_CTRL_SET_PERIODIC 0x3 /* 修改定时器为周期定时 */
    (3):控制定时器的形参,参数取值的含义根据第二个形参 cmd 来决定。
    (4):获取定时器延时的初始时间。
    (5):重置定时器的延时时间。
    (6):设置定时器为一次延时,即延时到期之后定时器就停止了。
    (7):设置定时器为周期延时,即延时到期之后又重新启动定时器。

    7、定时器启动函数

    定时器启动函数 rt_timer_start 在 timer.c 中定义,核心实现算法是将定时器按照延时时间做升序排列插入到系统定时器列表 rt_timer_list 中,具体代码:
    /**
    * 启动定时器
    * 
    * @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(); (1)
    
       /* 将定时器从系统定时器列表移除 */
       _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; (2)
    
       /* 关中断 */
       level = rt_hw_interrupt_disable();
    
    
       /* 将定时器插入到定时器列表 */
       /* 获取系统定时器列表根节点地址,rt_timer_list 是一个全局变量 */
      timer_list = rt_timer_list; (3)
    
    
       /* 获取系统定时器列表第一条链表根节点地址 */
       row_head[0] = &timer_list[0]; (4)
    
       /* 因为 RT_TIMER_SKIP_LIST_LEVEL 等于 1,这个循环只会执行一次 */
       for (row_lvl = 0; row_lvl < RT_TIMER_SKIP_LIST_LEVEL; row_lvl++) (5)
       {
       /* 列表不为空,当没有定时器被插入到系统定时器列表时,该循环不执行 */ (6)
       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; (6)-1
    
       /* 根据节点地址获取父结构的指针 */ (6)-2
       t = rt_list_entry(p, /* 节点地址 */
       struct rt_timer, /* 节点所在父结构的数据类型 */
       row[row_lvl]); /* 节点在父结构中叫什么,即名字 */
    
     
       /* 两个定时器的超时时间相同,则继续在定时器列表中寻找下一个节点 */
       if ((t->timeout_tick - timer->timeout_tick) == 0) (6)-3
      {
      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 是一个静态变量,用于记录启动了多少个定时器 */
       random_nr++;
       tst_nr = random_nr;
    
       /* 将定时器插入到系统定时器列表 */ (7)
       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],
       else
       break;
      tst_nr >>= (RT_TIMER_SKIP_LIST_MASK + 1) >> 1;
      }
    
      /* 设置定时器标志位为激活态 */
      timer->parent.flag |= RT_TIMER_FLAG_ACTIVATED; (8)
    
      /* 开中断 */
      rt_hw_interrupt_enable(level);
    
       return -RT_EOK;
    }

     8、定时器扫描函数

    定时器扫描函数 rt_timer_check 在 timer.c 中定义,用于扫描系统定时器列表,查询定时器的延时是否到期,如果到期则让对应的线程就绪,具体实现:
    /**
    * 该函数用于扫描系统定时器列表,当有超时事件发生时
    * 就调用对应的超时函数
    * 
    * @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(); (1)   /* 关中断 */   level = rt_hw_interrupt_disable(); (2)   /* 系统定时器列表不为空,则扫描定时器列表 */ (3)   while (!rt_list_isempty(&rt_timer_list[RT_TIMER_SKIP_LIST_LEVEL - 1]))   {   /* 获取第一个节点定时器的地址 */ (4)   t = rt_list_entry   (rt_timer_list[RT_TIMER_SKIP_LIST_LEVEL - 1].next, /* 节点地址 */   struct rt_timer, /* 节点所在的父结构的数据类型 */

      row[RT_TIMER_SKIP_LIST_LEVEL - 1]); /* 节点在父结构的成员名 */   if ((current_tick - t->timeout_tick) < RT_TICK_MAX / 2) (5)   {   /* 先将定时器从定时器列表移除 */   _rt_timer_remove(t); (6)   /* 调用超时函数 */   t->timeout_func(t->parameter); (7)   /* 重新获取 rt_tick */   current_tick = rt_tick_get(); (8)   /* 周期定时器 */ (9)   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);   }   /* 单次定时器 */ (10)   else   {   /* 停止定时器 */   t->parent.flag &= ~RT_TIMER_FLAG_ACTIVATED;   }   }   else   break; (11)
       /* 开中断 */
       rt_hw_interrupt_enable(level);
      }
    
    
    调用超时函数 rt_thread_timeout,将线程就绪。该函数在thread.c 中定义,具体实现
    /**
    * 线程超时函数
    * 当线程延时到期或者等待的资源可用或者超时时,该函数会被调用
    * 
    * @param parameter 超时函数的形参
    */
    void rt_thread_timeout(void *parameter)
    { 
      struct rt_thread *thread;
    
      thread = (struct rt_thread *)parameter;
    
      /* 设置错误码为超时 */ (1)
      thread->error = -RT_ETIMEOUT;
    
      /* 将线程从挂起列表中删除 */ (2)
       rt_list_remove(&(thread->tlist));
    
       /* 将线程插入到就绪列表 */ (3)
       rt_schedule_insert_thread(thread);
    
       /* 系统调度 */ (4)
       rt_schedule();
     }
  • 相关阅读:
    使用MobaXterm远程连接Ubuntu,启动Octave,界面不能正常显示
    ABP .Net Core 日志组件集成使用NLog
    ABP .Net Core Entity Framework迁移使用MySql数据库
    ABP前端使用阿里云angular2 UI框架NG-ZORRO分享
    阿里云 Angular 2 UI框架 NG-ZORRO介绍
    Visual Studio 2019 Window Form 本地打包发布猫腻
    VS Code + NWJS(Node-Webkit)0.14.7 + SQLite3 + Angular6 构建跨平台桌面应用
    ABP .Net Core 调用异步方法抛异常A second operation started on this context before a previous asynchronous operation completed
    ABP .Net Core To Json序列化配置
    .Net EF Core数据库使用SQL server 2008 R2分页报错How to avoid the “Incorrect syntax near 'OFFSET'. Invalid usage of the option NEXT in the FETCH statement.”
  • 原文地址:https://www.cnblogs.com/tansuoxinweilai/p/14965915.html
Copyright © 2011-2022 走看看