在上一篇博客中,学习了RT-Thread中线程的管理,其中主要包括理解优先级的概念,基于优先级的全抢占式调度策略,线程的5种状态(个人觉得也可以说是4种,因为运行态和就绪态其实是等同的)。另外还学习了各种状态转换的相关函数接口,静态线程动态线程的创建、脱离、删除等。同时学习了钩子函数的概念、调度器钩子的设置以及线程的相关控制函数(挂起、恢复、睡眠、修改等),然后还学习了空闲线程和主线程的相关概念以及区别。
在这篇博客中,开始学习RT-Thread中的时钟管理部分。
在之前的内核基础的博客中,就有提到:时钟节拍(OS Tick)是RT-Thread操作系统的最小时间单位。这一部分的介绍主要是为了了解到:
线程的延时、时间片轮转调度以及定时器超时等都需要时间的度量。在RT-Thread中,系统采用时钟节拍进行周期性的中断,这个中断可以看作是系统的心跳,应用不同,中断的时间健哥也不相同,一般在1ms到100ms,并且时钟节拍率越快,系统的额外开销就越大。从系统启动开始计数的时钟节拍成为系统时间。时钟节拍的长度可以根据RT_TICK_PER_SECOND的定义来调整,等于1/RT_TICK_PER_SECOND秒。
实现方式
时钟节拍的实现是由配置为中单处罚模式的硬件定时器产生的,当中断到来时,将调用一次void rt_tick_increase(void),通知操作系统已经过去一个系统时钟了,且不同硬件定时器中断实现都不同。在STM32中,采用的方法是设置一个全局变量rt_tick,每经过一个时钟节拍,值就会加1,rt_tick表示了系统从启动开始总共经过的时钟节拍数,即系统时间。此外,每经过一个时钟节拍时,都会检查当前线程的时间片是否用完,以及是否有定时器超时。
获取时钟节拍
时钟节拍的获取使用的函数是:
rt_tick_t rt_tick_get(void);
返回值是当前时钟的节拍值。此函数接口可以用于记录系统的运行时间长短或者测量某任务运行的时间。
定时器的概念类似于生活中的闹钟。可以分为:
1)硬件定时器
芯片本身提供的定时功能。一般由外部晶振提供给芯片输入时钟,芯片向软件模块提供一组配置寄存器,接收控制输入,到达设定时间后,芯片中断控制器产生时钟中断。精度很高,可达纳秒级,中断触发方式。
2)软件定时器
操作系统提供接口,构建在硬件定时器基础上,可提供不受数目限制的定时服务。以时钟节拍(OS Tick)的时间长度为单位,即定时数值必须是 OS Tick 的整数倍,例如一个 OS Tick 是 10ms,那么上层软件定时器只能是 10ms,20ms,100ms等,而不能定时为 15ms。
两种触发方式:
根据超时函数执行时的上下文环境,定时器可以分为:HARD_TIMER模式和SOFT_TIMER模式。
HARD_TIMER模式(RT-Thread默认方式)
该模式下的定时器超时函数在中断上下文环境中执行,可以在初始化/创建定时器时使用参数RT_TIMER_FLAG_HARD_TIMER来指定。
注意:对于超时函数,其执行时间应该尽可能短,执行时不能导致当前上下文挂起、等待。
SOFT_TIMER模式
可以通过宏RT_USING_TIMER_SOFT来决定是否启用该模式,该模式被启用后,系统会在初始化时创建一个timer线程,然后SOFT_TIMER模式的定时器超时函数都会在timer线程的上下文环境中执行。
两个重要的全局变量:
当有新的定时器Timer4,tick值为300时,当前系统rt_tick为30,则有下图:
定时器控制块
在 RT-Thread 操作系统中,定时器控制块由结构体 struct rt_timer 定义并形成定时器内核对象,再链接到内核对象容器中进行管理。它是操作系统用于管理定时器的一个数据结构,会存储定时器的一些信息,例如初始节拍数,超时时的节拍数,也包含定时器与定时器之间连接用的链表结构,超时回调函数等。
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;
定时器控制块由struct rt_timer结构体定义并形成定时器内核对象,再链接到内核对象容器中进行管理,list成员则用于把一个激活的(已经启动的)定时器连接到rt_timer_list链表中。
定时器跳表(Skip List)算法
跳表是一种基于并联链表的数据结构,实现简单,插入、删除、查找的时间复杂度均为 O(log n)。跳表是链表的一种,但它在链表的基础上增加了 “跳跃” 功能,正是这个功能,使得在查找元素时,跳表能够提供 O(log n) 的时间复杂度。
跳表算法是一种用空间换时间的一种算法,在RT-Thread中,根据宏定义RT_TIMER_SKIP_LIST_LEVEL来配置跳表的层数。
以上时定时器的工作方式,接下来时定时器的管理方式。通过对管理方式的学习,使得自己在代码层面有一定的理解。
对定时器的操作主要包括:创建/初始化定时器、启动定时器、运行定时器、删除/脱离定时器。所有定时器在定时超时后都会从定时器链表中被移除,儿周期性定时器会在它再次启动时被加入定时器链表,这与定时器参数的设置相关。
创建和删除定时器
与线程的创建类似,定时器的创建有静态创建和动态创建(线程叫做静态/动态线程)。
动态创建定时器使用的函数是:
rt_timer_t rt_timer_create(const char* name, //定时器名称
//超时函数指针
void (*timeout)(void* parameter),
//超时函数参数
void* parameter,
//定时器的超时时间,单位为时钟节拍
rt_tick_t time,
//定时器创建时的参数
//单次/周期定时、硬件/软件定时等,可用“或”关系取多值
rt_uint8_t flag);
定时器创建时的参数通过宏定义的方式给出:
#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_TIMER_FLAG_HARD_TIMER模式,定时器超时,回调函数将在时钟中断的服务例程上下文中被调用。
RT_TIMER_FLAG_SOFT_TIMER模式,定时器超时,回调函数将在系统时钟timer线程的上下文中被调用。
系统不再使用动态定时器时,可使用下面的函数接口对动态创建的定时器删除:
rt_err_t rt_timer_delete(rt_timer_t timer);
调用这个函数接口后,系统会把这个定时器从 rt_timer_list 链表中删除,然后释放相应的定时器控制块占有的内存。
初始化和脱离定时器
上面是动态创建定时器,这里时静态创建定时器,使用的接口是(参数含义参见动态创建参数):
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_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-Thread也可以使用控制函数来对定时器进行控制,控制函数如下:
rt_err_t rt_timer_control(rt_timer_t timer, rt_uint8_t cmd, void* arg);
其中cmd表示控制定时器的命令,当前支持的命令有:
#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 /* 设 置 定 时 器 为 周 期 型 定 时 器 */
对于定时器的应用示例,老规矩,不再这里贴出了,感兴趣的话可以自行查看官网 https://www.rt-thread.org/document/site/
感觉是时候回顾一下之前的内容了,不能只求走得快,还需要走得稳,一起加油!