时钟管理
时间是操作系统中至关重要的概念。操作系统需要通过时间来规范任务的执行,例如线程的延时、线程的时间片轮转调度以及定时器超时等。本章将介绍 RT-Thread 的时钟节拍和基于时钟节拍的定时器,阐述时钟节拍的产生原理,并指导如何使用 RT-Thread 的定时器。
时钟节拍
时钟节拍是操作系统中最小的时间单位,它是一种周期性的硬件中断。该中断可以被视为系统的心跳,中断之间的时间间隔取决于具体的应用需求,通常在 1 ms 到 100 ms 之间。时钟节拍率越高,系统的实时响应越快,但系统的开销也会相应增加。从系统启动开始计数的时钟节拍数称为系统时间。
在 RT-Thread 中,时钟节拍的长度可以通过宏定义 RT_TICK_PER_SECOND
来调整,其值等于 1 / RT_TICK_PER_SECOND
秒。
时钟节拍的实现方式
时钟节拍通常由配置为中断触发模式的硬件定时器产生。当硬件定时器产生中断时,系统会调用 void rt_tick_increase(void)
函数,通知操作系统已经过去了一个系统时钟节拍。不同的硬件定时器中断实现方式各不相同,以下代码以 STM32 定时器为例:
void SysTick_Handler(void)
{
/* 进入中断 */
rt_interrupt_enter();
rt_tick_increase();
/* 退出中断 */
rt_interrupt_leave();
}
在中断函数中,rt_tick_increase()
函数用于对全局变量 rt_tick
进行自增,其具体代码如下:
void rt_tick_increase(void)
{
struct rt_thread *thread;
/* 全局变量 rt_tick 自加 */
++ rt_tick;
/* 检查时间片 */
thread = rt_thread_self();
-- thread->remaining_tick;
if (thread->remaining_tick == 0)
{
/* 重新赋初值 */
thread->remaining_tick = thread->init_tick;
/* 线程让出处理器 */
rt_thread_yield();
}
/* 检查定时器 */
rt_timer_check();
}
全局变量 rt_tick
会在每个时钟节拍到来时自增 1,rt_tick
的值表示系统从启动以来总共经过的时钟节拍数,即系统时间。此外,在每个时钟节拍到来时,系统还会检查当前线程的时间片是否用完以及是否有定时器超时。
注意:
rt_timer_check()
函数在中断中被调用,用于检查系统定时器链表,如果发现有定时器超时,则会调用相应的超时函数。所有定时器在超时后都会被从定时器链表中移除,而周期性定时器会在再次启动时被加入定时器链表。
获取时钟节拍
全局变量 rt_tick
记录了系统从启动开始经过的总节拍数。可以使用 rt_tick_get()
函数获取当前 rt_tick
的值,从而获取当前的时钟节拍值。该接口常用于记录系统运行时间或测量任务的执行时间。函数接口如下:
rt_tick_t rt_tick_get(void);
rt_tick_get()
函数的返回值说明如下:
返回值 | 描述 |
---|---|
rt_tick |
当前时钟节拍值。 |
定时器管理
定时器是指从指定的时刻开始,经过一定的时间后触发一个事件的机制。定时器分为硬件定时器和软件定时器:
-
硬件定时器: 由芯片本身提供的定时功能,通常由外部晶振提供时钟输入。芯片提供一组配置寄存器,用于接受控制输入。当达到设定的时间值时,芯片的中断控制器会产生时钟中断。硬件定时器的精度通常很高,可达纳秒级别,且采用中断触发方式。
-
软件定时器: 由操作系统提供的系统接口,它基于硬件定时器实现,可以提供不受数目限制的定时器服务。
RT-Thread 提供了基于软件实现的定时器,定时单位为时钟节拍 (OS Tick) 的整数倍。例如,如果一个 OS Tick 是 10ms,那么定时器的时间只能是 10ms、20ms、100ms 等,而不能是 15ms。
RT-Thread 定时器介绍
RT-Thread 的定时器提供两种触发方式:
-
单次触发定时器: 启动后只会触发一次定时器事件,然后自动停止。
-
周期触发定时器: 会周期性地触发定时器事件,除非用户手动停止,否则将一直持续执行。
根据定时器超时函数执行时所处的上下文环境,RT-Thread 的定时器可以分为 HARD_TIMER
模式和 SOFT_TIMER
模式:
HAER_TIMER模式
HARD_TIMER
模式的定时器超时函数在中断上下文中执行。可以通过在初始化/创建定时器时使用 RT_TIMER_FLAG_HARD_TIMER
参数来指定。
在中断上下文中执行时,超时函数的要求与中断服务例程的要求相同:执行时间应尽量短,不应导致当前上下文挂起或等待。例如,在中断上下文中执行的超时函数不应尝试申请或释放动态内存等。
RT-Thread 定时器默认采用 HARD_TIMER
模式。
SOFT_TIMER模式
SOFT_TIMER
模式可通过宏定义 RT_USING_TIMER_SOFT
来使能。当该模式启用后,系统会在初始化时创建一个 timer 线程,并且所有 SOFT_TIMER
模式的定时器超时函数都会在 timer 线程的上下文中执行。可以通过在初始化/创建定时器时使用 RT_TIMER_FLAG_SOFT_TIMER
参数来指定。
定时器工作机制
RT-Thread 定时器模块维护两个重要的全局变量:
rt_tick
:当前系统经过的 tick 时间,每当硬件定时器中断发生时,该值会加 1。rt_timer_list
:定时器链表,系统新创建并激活的定时器会按照超时时间排序,插入到rt_timer_list
链表中。
例如,当系统当前的 rt_tick
值为 20,并且系统中已创建并启动了三个定时器,分别为:定时 50 个 tick 的 Timer1
,100 个 tick 的 Timer2
和 500 个 tick 的 Timer3
。这三个定时器分别加上系统当前时间 rt_tick=20
后,将按照超时时间从小到大的顺序链接到 rt_timer_list
链表中。
当 rt_tick
随着硬件定时器的触发持续增长时,当 rt_tick
从 20 增长到 70 时,会触发 Timer1
的超时函数,同时将 Timer1
从 rt_timer_list
链表中删除。同理,在经过 100 个 tick 和 500 个 tick 后,会分别触发 Timer2
和 Timer3
的超时函数,并将其从链表中删除。
如果在 rt_tick = 30
时,有任务新创建了一个 tick 值为 300 的 Timer4
定时器,由于 Timer4
的超时时间为 rt_tick + 300 = 330
,因此它将被插入到 Timer2
和 Timer3
之间。
定时器控制块
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;
其中,list
成员用于将激活的定时器链接到 rt_timer_list
链表中。
定时器跳表算法(Skip List)算法
如前所述,系统新创建并激活的定时器会按照超时时间排序,插入到 rt_timer_list
链表中。rt_timer_list
是一个有序链表。RT-Thread 使用跳表算法来加速链表元素的搜索。
跳表是一种基于并联链表的数据结构,实现简单,插入、删除、查找的时间复杂度均为 O(log n)。跳表在链表的基础上增加了“跳跃”功能,从而实现快速查找。
例如,在一个有序链表中搜索元素 {13, 39},需要比较的次数分别是 {3, 5},总共比较 8 次。
使用跳表算法后,可以提取部分节点作为索引。例如,提取 {3, 18, 77} 作为一级索引。这样搜索元素 39 时仅需比较 3 次。可以继续提取一级索引的元素作为二级索引,进一步加快搜索速度。
跳表通过上层索引,减少搜索时的比较次数,提高查找效率,这是一种以空间换取时间的算法。RT-Thread 中通过宏定义 RT_TIMER_SKIP_LIST_LEVEL
配置跳表的层数,默认为 1,表示采用一级有序链表算法。每增加 1,表示在原链表基础上增加一级索引。
定时器的管理方式
本节将介绍 RT-Thread 定时器的相关接口,帮助读者在代码层面理解定时器的工作原理。
系统启动时需要初始化定时器管理系统。可以通过以下函数接口完成:
void rt_system_timer_init(void);
如果需要使用 SOFT_TIMER
模式,则需要在系统初始化时调用以下函数:
void rt_system_timer_thr