RT-thread内核之定时器管理

一、前言

rt-thread采用软件定时器线程模式或硬件定时器中断模式来实现系统定时器管理。而rt-thread操作系统在默认情况下是采用的硬件定时器中断模式的方式,用户可以通过宏定义RT_USING_TIMER_SOFT来修改定时器管理模式。

硬件定时器中断模式是利用MCU芯片本身提供的硬件定时器功能,一般是由外部晶振提供给芯片输入时钟,芯片向软件模块提供一组配置寄存器,接受控制输入,到达设定时间值后芯片中断控制器产生时钟中断(比如stm32的嘀嗒定时器中断),在硬件定时器中断服务中检查rt-thread系统定时器是否超时。硬件定时器中断模式的精度一般很高,可以达到纳秒级别,并且是中断触发方式(如滴答定时器中断,其他MCU硬件定时器中断)。

软件定时器线程模式是指由操作系统提供的一类系统接口,它构建在MCU硬件定时器基础之上,使系统能够提供不受数目限制的定时器服务。在该模式中定时器的超时检查在线程入口函数中进行,但定时器的精度仍取决于MCU硬件定时器精度,因为在此模式下系统当前时钟计数rt_tick仍然在MCU硬件定时器中断服务(如stm32的嘀嗒定时器中断)中递增,定时器超时检查时需要比较rt_tick与timeout_tick。

二、定时器基本工作原理

无论是软件定时器线程模式,还是硬件定时器中断模式, 在rt-thread定时器模块中都维护两个变量:1、当前系统的时间点rt_tick(当MCU硬件定时器中断时加1);2、定时器链表rt_soft_timer_list(软件定时器线程模式)以及rt_timer_list(硬件定时器中断模式)。在两种模式下,定时器超时检查函数中一旦检查到定时器超时,则先将该定时器从链表中移除,然后执行超时函数后。定时器在创建或初始化时默认为单次定时,若此时定时器内核对象标志设为RT_TIMER_FLAG_PERIODIC,则执行超时函数后会重新启动该定时器即将该定时器重新加入定时器链表中。

在硬件定时器中断模式下不存在定时器线程,系统中新创建的定时器都会被按照超时时间点timeout_tick从小到大排序的方式插入到rt_timer_list链表中,rt_timer_list的每个节点保留了一个定时器的信息,并且在这个节点加入定时器链表之前就计算好了定时器的超时时间点,即timeout_tick。在MCU硬件定时器中断服务中,除了rt_tick加1以外,还通过定时器超时检查函数rt_timer_check检查定时器链表rt_timer_list中的定时器是否超时,即rt_tick是否赶上timeout_tick,若定时器超时,则调用定时器超时函数。

在软件定时器线程模式下则存在定时器线程,系统中新创建的定时器都会被按照超时时间点timeout_tick从小到大排序的方式插入到rt_soft_timer_list链表中,rt_soft_timer_list的每个节点保留了一个定时器的信息,并且在这个节点加入定时器链表之前就计算好了定时器的超时时间点,即timeout_tick。在线程入口函数rt_thread_timer_entry中通过不断获取当前rt_tick值,将其与定时器超时时间点timeout_tick对比从而判断定时器是否超时,并进行定时器超时检查函数,一旦发现定时器超时就调用定时器超时函数rt_soft_timer_check,即定时器超时处理函数。

三、定时器管理控制块:在include/rtdef.h中定义

/**
 * clock & timer macros
 */
#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             /**< 硬件定时器中断模式,定时器超时检查及超时函数调用在MCU硬件定时器中断服务中执行,默认 */
#define RT_TIMER_FLAG_SOFT_TIMER        0x4             /**< 软件定时器线程模式,定时器超时检查及超时函数调用在定时器线程入口函数中执行 */

#define RT_TIMER_CTRL_SET_TIME          0x0             /**< set timer control command */
#define RT_TIMER_CTRL_GET_TIME          0x1             /**< get timer control command */
#define RT_TIMER_CTRL_SET_ONESHOT       0x2             /**< change timer to one shot */
#define RT_TIMER_CTRL_SET_PERIODIC      0x3             /**< change timer to periodic */

#ifndef RT_TIMER_SKIP_LIST_LEVEL
#define RT_TIMER_SKIP_LIST_LEVEL          1
#endif

/* 1 or 3 */
#ifndef RT_TIMER_SKIP_LIST_MASK
#define RT_TIMER_SKIP_LIST_MASK         0x3
#endif

/**
 * timer structure
 */
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;                //定时器超时时间点,即超时那一刻的时间点
};
typedef struct rt_timer *rt_timer_t;

四、软件定时器线程模式相关函数:在src/timer.c中

软件定时器线程初始化:
void rt_system_timer_thread_init(void);
在该函数中初始化软件定时器线程模式下定时器链表数组,以及初始化软件定时器线程。
软件定时器线程入口函数:
/* system timer thread entry */                 //软件定时器线程入口函数
static void rt_thread_timer_entry(void *parameter)
{
    rt_tick_t next_timeout;

      while (1)//软件定时器优先级设置最高优先级0,且线程入口函数中为死循环,因此若函数中没有挂起自身线程和执行线程调度,则始终只运行这个线程
    {
        /* get the next timeout tick */         //得到软件定时器线程模式中定时器链表的下一个定时器的超时时间点
        next_timeout = rt_timer_list_next_timeout(rt_soft_timer_list);
        if (next_timeout == RT_TICK_MAX)        //定时器链表为空,即无定时器。RT_TICK_MAX is defined to be 0xffffffff in rtdef.h
        {
            /* 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);                //休眠一段时间,delay函数将自身线程挂起并启动自身线程定时器后,执行线程调度运行其他就绪线程
            }
        }

        /* check software timer */
        rt_soft_timer_check(); //预计的时间到了,检查是否该产生定时器超时事件。以前的版本中在这里添加了调度器锁(先进入临界区,检查完后再退出临界区)
    }
}
定时器超时检查函数:
void rt_soft_timer_check(void);在该函数中扫描定时器链表rt_soft_timer_list中产生超时的定时器,将其移除定时器链表并执行定时器超时函数,若为周期性定时器则重新启动该定时器,即重新将其加入定时器链表中。

上面代码中,为什么定时器超时检查函数中判断定时器超时的条件是((current_tick - t→timeout_tick) < RT_TICK_MAX/2)?

因为系统时钟rt_tick溢出后会自动回绕,取定时器比较最大值是定时器最大值的一半,即RT_TICK_MAX/2(在比较两个定时器值时,值是32位无符号数,相减运算将会自动回绕)。

由此可见,rt-thread系统支持的定时器最长定时时间为RT_TICK_MAX/2,即248天(10ms/tick),124天(5ms/tick),24.5天(1ms/tick)。

五、硬件定时器中断模式相关函数:在src/timer.c中

定时器超时检查函数:
void rt_timer_check(void);该函数在MCU硬件定时器中断函数中调用,主要功能为扫描定时器链表rt_timer_list中产生超时的定时器,将其移除定时器链表并执行定时器超时函数,若为周期性定时器则重新启动该定时器,即重新将其加入定时器链表中。此函数与rt_soft_timer_check基本大致相同,只不过一个是查找硬件定时器中断模式中定时器链表rt_timer_list,一个是查找软件定时器线程模式中定时器链表rt_soft_timer_list.
得到下一定时器超时时间点:
rt_tick_t rt_timer_next_timeout_tick(void)
{
    return rt_timer_list_next_timeout(rt_timer_list);//得到硬件定时器中断模式中定时器链表的下一个定时器的超时时间点
}

六、定时器通用函数接口:在src/timer.c中

定时器创建:
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)//定时器内核对象标志

#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 /* 默认为硬件定时器中断模式 */#define RT_TIMER_FLAG_SOFT_TIMER  0x4 /* 软件定时器线程模式      */
定时器删除:
rt_err_t rt_timer_delete(rt_timer_t timer);调用这个函数接口后,系统会把这个定时器从rt_timer_list链表中删除,然后释放相应的定时器控制块占有的内存
定时器脱离:
rt_err_t rt_timer_detach(rt_timer_t timer);脱离定时器时,系统会把定时器对象从系统容器的定时器链表中删除,但是定时器对象所占有的内存不会被释放。
定时器启动:
rt_err_t rt_timer_start(rt_timer_t timer);
当定时器被创建或者初始化以后,并不会被立即启动,必须在调用启动定时器函数接口后,才开始工作。
调用定时器启动函数接口后,定时器的状态将更改为激活状态(RT_TIMER_FLAG_ACTIVATED),并按照超时顺序插入到rt_timer_list队列链表中。

定时器停止:
rt_err_t rt_timer_stop(rt_timer_t timer);
调用定时器停止函数接口后,定时器状态将更改为停止状态,并从rt_timer_list链表中脱离出来不参与定时器超时检查。当一个(周期性)定时器超时时,也可以调用这个函数接口停止这个(周期性)定时器本身。
定时器控制:
rt_err_t rt_timer_control(rt_timer_t timer, rt_uint8_t cmd, void *arg);
#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 /* 设置定时器为周期型定时器 */
时间: 2024-08-02 10:59:25

RT-thread内核之定时器管理的相关文章

Java 线程第三版 第一章Thread导论、 第二章Thread的创建与管理读书笔记

第一章 Thread导论 为何要用Thread ? 非阻塞I/O I/O多路技术 轮询(polling) 信号 警告(Alarm)和定时器(Timer) 独立的任务(Task) 并行算法 第二章 Thread的创建与管理 一.什么是Thread ? Thread是所在主机执行的应用程序任务(task). 只有一个线程的例子: public class Factorial { public static void main(String[] args) { int n = 5; System.ou

深入Linux内核架构——进程管理和调度(上)

如果系统只有一个处理器,那么给定时刻只有一个程序可以运行.在多处理器系统中,真正并行运行的进程数目取决于物理CPU的数目.内核和处理器建立了多任务的错觉,是通过以很短的间隔在系统运行的应用程序之间不停切换做到的.由此,以下两个问题必须由内核解决:除非明确要求,否则应用程序不能彼此干扰:CPU时间必须在各种应用程序之间尽可能公平共享(一些程序可能比其他程序更重要).本篇博文主要涉及内核共享CPU时间的方法以及如何在进程之间切换(内核为各进程分配时间,保证切换之后从上次撤销其资源时执行环境完全相同)

RT Thread学习历程(1):串口乱码问题

因为学习实时系统,最近接触到RT Thread. 把RT Thread官网上的示例代码烧录到STM32的板子上之后,在串口软件上接收到的全是乱码,一开始以为是串口软件的问题,换了2个软件之后情况都一样,最后发现是晶振的问题,我用的是STM32F407VGT6,晶振要设为8MHz,代码相应的设置晶振的部分也要修改.

Linux内核之内存管理(4)--缺页处理程序

本文主要解说缺页处理程序,凝视足够具体,不再解释. //以下函数将一页内存页面映射到指定线性地址处,它返回页面的物理地址 //把一物理内存页面映射到线性地址空间指定处或者说把线性地址空间指定地址address处的页面映射到主内存区页面page上.主要工作是在相关也文件夹项和页表项中设置指定页面的信息.在处理缺页异常函数do_no_page中会调用这个函数. 參数:address--线性地址:page--是分配的主内存区中某一页面指针 static unsigned long put_page(u

Linux0.12内核之内存管理(2)

本文主要介绍Linux0.12内核memory.c中的函数 1.void free_page(unsigned long addr) //释放物理地址addr处的一页内存.用于free_page_tables()函数 void free_page(unsigned long addr) { //首先判定给定物理地址的合理性.如果物理地址addr小于内存低端1M,对此不///予处理.如果addr>=内存最高端,则显示出错并且内核停止工作 if(addr<LOW_MEM) return; if(a

Linux 0.12内核与现代内核在内存管理上的区别

0.12内核的内存管理比较简单粗暴,内核只用了一个页目录,只能映射4G的线性空间,所以每个进程的虚拟空间(逻辑空间)只能给到64M,最多64个进程:每个进程都有对应的任务号nr,当一个进程需要分配进程空间时,只需要nr乘以64M就可以得出该进程空间的线性起始地址.然后该进程的代码段.数据段描述符里面的基址字段会被设定为(nr x 64M),同时可以为进程分配页目录项和页目录表用以承载映射关系. 之后如果进程要访问自己空间内的某个地址时就会首先用基地址与程序内32位偏移地址(逻辑地址)合成出线性地

Linux系统启动流程、内核及模块管理

Linux系统启动流程.内核及模块管理 Linux系统的组成部分组成:内核+根文件系统(kernel+rootfs)内核(kernel): 进程管理(创建.调度.销毁等).内存管理.网络管理(网络协议栈).驱动程序.文件系统.安全功能IPC:Inter Process Communication机制本地进程间通信机制:消息队列.semerphor.shm(共享内存)跨主机进程间通信机制:socket等运行中的系统环境可分为两层:内核空间.用户空间内核空间(模式):内核代码(特权级操作-->系统调

Linux0.12内核之内存管理(3)

本系列的第三篇文章主要来介绍与共享物理页面相关的两个函数. //在发生缺页异常的时,首先看看能否与运行同一个文件的其他进程进行页面共享处理.该函数首先判断系统中是否有另外进程也在运行与当前进程一样的执行文件.若有,则在系统当前任务中找寻这样的任务.若找到了这样的任务就尝试与其共享指定地址处的页面.判断系统中是否有另一个进程也在执行同一个可执行文件的方法是利用进程任务数据结构中的executable字段.该字段执行进程正在执行程序的内存中的i节点,根据该i节点的引用次数i_count我们就可以判断

java Timer定时器管理类

1.java timer类,定时器类.启动执行定时任务方法是timer.schedule(new RemindTask(), seconds*1000);俩参数分别是TimerTask子类,具体执行定时操作所执行的动作,后一个参数是豪秒,代表间隔多久执行一次.2.TimerTask类,java.util.TimerTask,具体定时执行的操作.里面有个run方法,和线程run方法一样(就是线程).5.Timer是一种定时器工具,用来在一个后台线程计划执行指定任务.它可以计划执行一个任务一次或反复