目录
1 前言
2 时钟节拍
时钟节拍用于系统处理所有和时间有关的事件,如线程的延时、线程的时间片轮转调度以及定时器超时等,可以理解为内核的心跳。
2.1 RT_TICK_PER_SECOND
时钟节拍都由一个固定的硬件定时器来实现,该定时器设定固定的超时时间,一般为1~100ms。时钟节拍率越快,系统的额外开销就越大,但系统的响应速度会更快。
该时间可 以 根 据 RT_TICK_PER_SECOND 的 定 义 来 调 整, 等 于 1/ RT_TICK_PER_SECOND 秒。
- 可以通过menuconfig设置RT_TICK_PER_SECOND
- 或者直接修改rtconfig.h中的RT_TICK_PER_SECOND定义
#define RT_TICK_PER_SECOND 1000
2.2 rt_tick
- 内核通过一个全局变量rt_tick来记录时钟节拍,在每次时钟节拍中断中都会自加一次。
static volatile rt_tick_t rt_tick = 0;
- 内核提供获取时钟节拍的接口rt_tick_get(void),用于内核一些超时处理的判定。
rt_tick_t rt_tick_get(void) { /* return the global tick */ return rt_tick; }
2.3 SysTick_Handler
时钟节拍中断回调函数,本质就是某个硬件定时器的中断回调;每个硬件平台移植RTthread时都需要移植该函数,下面以stm32平台分析。主要工作如下:
- 全局变量时钟节拍计数器rt_tick自加1
- 检查当前线程时间片是否耗尽,如果耗尽则先去做线程调度操作
- 检查定时器链表并做相应处理
void SysTick_Handler(void)
{
/* enter interrupt */
rt_interrupt_enter();
//只用于stm32,该平台驱动库会使uwTick来做一些驱动超时处理
if(SysTick->CTRL & SysTick_CTRL_COUNTFLAG_Msk)
HAL_IncTick();
//时钟节拍具体处理函数
rt_tick_increase();
/* leave interrupt */
rt_interrupt_leave();
}
1)rt_tick_increase
void rt_tick_increase(void)
{
struct rt_thread *thread;
rt_base_t level;
level = rt_hw_interrupt_disable();
//1. rt_tick自加,分多核和单核场景 */
#ifdef RT_USING_SMP
rt_cpu_self()->tick ++;
#else
++ rt_tick;
#endif /* RT_USING_SMP */
//2. 检查当前线程时间片
thread = rt_thread_self();
-- thread->remaining_tick;
if (thread->remaining_tick == 0)
{
//2.1 当前线程时间片超时,修改线程状态为RT_THREAD_STAT_YIELD
thread->remaining_tick = thread->init_tick;
thread->stat |= RT_THREAD_STAT_YIELD;
//2.2 执行线程调度
rt_hw_interrupt_enable(level);
rt_schedule();
}
else
{
rt_hw_interrupt_enable(level);
}
//3 检查定时器
rt_timer_check();
}
2.4 rt_tick溢出问题
内核使用全局变量rt_tick_t rt_tick记录节拍,对于32位系统,rt_tick最大为0xFFFFFFFF。按10ms一个节拍计算,大概497天时rt_tick就会溢出。在定时器使用逻辑中,一般定时器启动时设定超时时间为next_time,满足current_time >= next_time就认为定时器超时,但如果在溢出前后,就会出现错误判断。
- 内核是如此解决的
//等效于无论是否回绕都满足current_tick >= t->timeout_tick if ((current_tick - t->timeout_tick) < RT_TICK_MAX / 2)
PS:该方式有个限制,就是定时器超时时间设定必须小于RT_TICK_MAX/2,否则上述判断就会出现错误。
3 定时器实现
内核提供了完善的软件定时器功能,以时钟节拍(OS Tick)的时间长度为单位,即定时器的超时时间必须是一个节拍的整数倍。
3.1 HARD_TIMER/SOFT_TIMER
- 内核依据定时器超时函数执行位置,将分定时器分为HARD_TIMER和SOFT_TIMER模式。可以在创建定时器时使用参数选择。
- HARD_TIMER模式下,超时函数直接在中断上下文执行,对于超时函数的要求与中断服务例程的要求相同:执行时间应该尽量短,执行时不应导致当前上下文挂起、等待。
- SOFT_TIMER模式需要先开启设置才能使用,内核会单独开启一个线程_timer_thread,将SOFT_TIMER模式的超时函数都放到该线程执行。
3.2 工作机制
- 所有定时器都是基于rt_tick来记录经历的时间。rt_tick固定每个时钟节拍自加+1
- 内核维护两个定时器链表:_soft_timer_list和_timer_list。新创建的定时器都按超时时间排序的方式插入到定时器链表。HARD_TIMER/SOFT_TIMER模式的定时器统一放到链表_timer_list/_soft_timer_list管理。
- 例如当前rt_tick为20,此时创建并启动了三个定时器,分别是定时时间为50 个 tick 的 Timer1、100 个 tick 的 Timer2 和 500 个 tick 的 Timer3,这三个定时器分别加上系统当前时间 rt_tick=20,从小到大排序链接在 rt_timer_list 链表中,形成下图的定时器链表结构。
- 每次时钟节拍中断中,rt_tick会自加1,并和链表中的定时器超时时间进行比较,如果rt_tick>=timeout则说明该定时器超时,则处理对应的回调函数
3.3 跳表算法
前面已经分析过,定时器在启动时会按超时时间排序插入到链表中,如果从头开始遍历链表,效率很低时间复杂度O(n)。当使用定时器数量比较大时,每次新创建一个定时器并启动时,会有很大的开销。内核选择使用跳表算法来加速遍历速度。
- 跳表是一种基于并联链表的数据结构,利用空间换取时间的办法,可以实现二分查找的有序链表,插入、删除、查找的时间复杂度均为 O(log n)
- 跳表如何实现
1)原始有序单链表如下
2)从链表中每两个元素抽出来,加一级索引,一级索引指向了原始链表
3)同样查找10,原始链表需要遍历1,3,4,5,7,8,9->10;而跳表中只需要先遍历一级索引1,4,7,9->10
1)RT_TIMER_SKIP_LIST_LEVEL
在 RT-Thread 中通过宏定义 RT_TIMER_SKIP_LIST_LEVEL 来配置跳表的层数,默认为 1,默认不使用跳表。
3.4 结构体定义
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; //创建时 超时周期+当前系统tick
};
typedef struct rt_timer *rt_timer_t;
4 定时器接口分析
4.1 定时器创建
- 分为动态创建rt_timer_create()和静态创建rt_timer_init();区别在于rt_timer结构体在内核动态创建还是外部静态声明
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)//定时器内核对象标志
- 创建时,参数重点需要关注flag,b[1]置1表示周期定时器,置0表示单次定时器;b[2]置1表示SOFT_TIMER,置0表示HARD_TIMER;相互之间可以使用“或”同时赋值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 /* 软 件 定 时 器 */
PS:需要注意的是,如果使用SOFT_TIMER,需要内核配置先打开RT_USING_TIMER_SOFT,否则即使flag使能0x4,但还是会按HARD_TIMER处理,代码使用宏RT_USING_TIMER_SOFT来屏蔽SOFT_TIMER功能
1) _timer_init()
rt_timer_create()和rt_timer_init()最终会调用_timer_init()完成创建,在该函数中对定时器结构体数据进行初始化
4.2 定时器启动
rt_err_t rt_timer_start(rt_timer_t timer)
{
//1 关闭中断
level = rt_hw_interrupt_disable();
//2 如果定时器在定时器链表中,先移除
_timer_remove(timer);
//3 将定时器超时周期+当前系统tick
RT_ASSERT(timer->init_tick < RT_TICK_MAX / 2); //超时时间最大不能超过RT_TICK_MAX/2,这个已经在rt_tick溢出问题中分析过
timer->timeout_tick = rt_tick_get() + timer->init_tick;
//4 通过定时器类型决定使用哪个定时器链表来管理
#ifdef RT_USING_TIMER_SOFT
if (timer->parent.flag & RT_TIMER_FLAG_SOFT_TIMER)
{
/* insert timer to soft timer list */
timer_list = _soft_timer_list;
}
else
#endif /* RT_USING_TIMER_SOFT */
{
/* insert timer to system timer list */
timer_list = _timer_list;
}
//5 赋值定时器链表,从链表头开始
row_head[0] = &timer_list[0];
//RT_TIMER_SKIP_LIST_LEVEL默认1,则只执行一次,不做跳表算法
for (row_lvl = 0; row_lvl < RT_TIMER_SKIP_LIST_LEVEL; row_lvl++)
{
//6 从链表头开始依次遍历定时器链表,如果为空则直接跳过
for (; row_head[row_lvl] != timer_list[row_lvl].prev;
row_head[row_lvl] = row_head[row_lvl]->next)
{
struct rt_timer *t;
//6.1 获取下一个链表节点
rt_list_t *p = row_head[row_lvl]->next;
//6.2 依据链表节点获取定时器结构体
t = rt_list_entry(p, struct rt_timer, row[row_lvl]);
//6.3 如果超时时间相同,则谁先启动谁就排序在前面
if ((t->timeout_tick - timer->timeout_tick) == 0)
{
continue;
}
//6.3 当下一个链表节点对应的定时器,其超时时间大于需启动timer时,表示已找到需插入的位置
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;
}
//7 将需启动的定时器timer插入到步骤6中遍历的节点后
rt_list_insert_after(row_head[RT_TIMER_SKIP_LIST_LEVEL - 1],
&(timer->row[RT_TIMER_SKIP_LIST_LEVEL - 1]));
//8 设置定时器状态
timer->parent.flag |= RT_TIMER_FLAG_ACTIVATED;
//9 如果启动定时器是SOFT_TIMER则唤醒_timer_thread线程去处理
#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);
need_schedule = RT_TRUE;
}
}
#endif /* RT_USING_TIMER_SOFT */
rt_hw_interrupt_enable(level);
if (need_schedule)
{
rt_schedule();
}
}
- 在整个定时器启动过程中是一直关闭中断的rt_hw_interrupt_disable()
- 依据当前系统rt_tick和定时器超时周期,得出定时器基于系统tick的超时时间赋值给timer->timeout_tick
- 依据定时器类型SOFT_TIMER/HARD_TIMER,选择对应的定时器链表
- 从定时器链表表头开始,依次遍历,依据超时时间按从小到大的顺序,将定时器插入链表中(超时时间一样的定时器,谁先启动的谁排序在前面)
- 如果是SOFT_TIMER,则唤醒_timer_thread线程,并执行任务调度
4.3 定时器停止
rt_err_t rt_timer_stop(rt_timer_t timer)
{
//1. 关闭中断
level = rt_hw_interrupt_disable();
//2. 将定时器从链表中移除,并且修改定时器状态
_timer_remove(timer);
timer->parent.flag &= ~RT_TIMER_FLAG_ACTIVATED;
//3. 打开中断
rt_hw_interrupt_enable(level);
return RT_EOK;
}
- 函数执行过程中是屏蔽中断的
- 将定时器从链表中移除,并修改状态为未激活
4.4 定时器删除
rt_err_t rt_timer_delete(rt_timer_t timer)
{
//1. 关闭中断
level = rt_hw_interrupt_disable();
//2. 将定时器从管理链表中移除,并修改状态为未激活
_timer_remove(timer);
timer->parent.flag &= ~RT_TIMER_FLAG_ACTIVATED;
//3. 打开中断
rt_hw_interrupt_enable(level);
//4. 将定时器对象从内核对象容器中脱离,并释放对象所占用的内存
rt_object_delete(&(timer->parent));
return RT_EOK;
}
4.5 定时器脱离
rt_err_t rt_timer_detach(rt_timer_t timer)
- 相比定时器删除,唯一的区别就是不释放对象所占用的内存
5 定时器超时处理
5.1 HARD_TIMER定时器
HARD_TIMER定时器是在rt_timer_check()函数中进行超时处理的,该函数在每次时钟节拍中断SysTick_Handler中执行
1) rt_timer_check
void rt_timer_check(void)
{
//1 初始化一个临时列表
rt_list_init(&list);
//2 获取当前系统节拍
current_tick = rt_tick_get();
//3 关闭中断
level = rt_hw_interrupt_disable();
//4 从链表头开始遍历,处理定时器超时事件
while (!rt_list_isempty(&_timer_list[RT_TIMER_SKIP_LIST_LEVEL - 1]))
{
//4.1 获取链表节点对应的定时器结构体
t = rt_list_entry(_timer_list[RT_TIMER_SKIP_LIST_LEVEL - 1].next,
struct rt_timer, row[RT_TIMER_SKIP_LIST_LEVEL - 1]);
//4.2 如果当前系统时间大于定时器超时时间,则认为定时器超时并进行相应处理
if ((current_tick - t->timeout_tick) < RT_TICK_MAX / 2)
{
//4.2.1 暂时将该定时器从管理链表中移除
_timer_remove(t);
//4.2.2 将该定时器插入到临时链表list中,作用后面会体现
rt_list_insert_after(&list, &(t->row[RT_TIMER_SKIP_LIST_LEVEL - 1]));
//4.2.3 执行超时回调
t->timeout_func(t->parameter);
//4.2.4 再次获取系统时间(上面回调函数是对外给用户,时间开销可能会很大)
current_tick = rt_tick_get();
//4.2.5 结合步骤4.2.2,如果此时定时器不在临时链表list中,说明用户在超时回调中有调用start或者detach,
//那么该定时器也就无需后续处理,可以直接遍历下一个定时器
if (rt_list_isempty(&list))
{
continue;
}
//4.2.6 将定时器从临时链表list中移除
rt_list_remove(&(t->row[RT_TIMER_SKIP_LIST_LEVEL - 1]));
//4.2.7 如果是周期定时器,则再次启动
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);
}
}
//4.3 否则则直接退出遍历,因为链表是按超时时间从小到大顺序排列的,只要当前系统时间小于该节点超时时间,后续节点都不需要再比较了
else break;
}
//5 打开中断
rt_hw_interrupt_enable(level);
}
- HARD_TIMER定时器的超时回调是在时钟节拍中断中执行,需严格满足中断上下文执行要求
- 整个过程处于中断关闭状态
- 从定时器管理链表_timer_list头开始遍历,通过当前系统时间大于节点定时器超时时间,则执行回调函数;并对周期性定时器进行再启动处理
5.2 SOFT_TIMER定时器
SOFT_TIMER定时器是在线程_timer_thread_entry中做超时处理,该线程优先级是由RT_TIMER_THREAD_PRIO决定,堆栈大小是由RT_TIMER_THREAD_STACK_SIZE决定。该线程会在内核启动时调用rt_system_timer_thread_init()完成初始化
void rt_system_timer_thread_init(void)
{
#ifdef RT_USING_TIMER_SOFT
//1 初始化软件定时器管理链表
for (i = 0;i < sizeof(_soft_timer_list) / sizeof(_soft_timer_list[0]);i++)
{
rt_list_init(_soft_timer_list + i);
}
//2 初始化线程并启动
rt_thread_init(&_timer_thread,
"timer",
_timer_thread_entry,
RT_NULL,
&_timer_thread_stack[0],
sizeof(_timer_thread_stack),
RT_TIMER_THREAD_PRIO,
10);
rt_thread_startup(&_timer_thread);
#endif /* RT_USING_TIMER_SOFT */
}
1) _timer_thread_entry
static void _timer_thread_entry(void *parameter)
{
while (1)
{
//1 获取软件定时器链表第一个节点的超时时间(定时器是按超时时间从小到大的顺序排列)
next_timeout = _timer_list_next_timeout(_soft_timer_list);
//2 链表为空时,_timer_list_next_timeout返回RT_TICK_MAX
if (next_timeout == RT_TICK_MAX)
{
//2.1 说明无软件定时器需管理,则该线程主动挂起;当软件定时器启动时,会唤醒再次该线程
rt_thread_suspend(rt_thread_self());
rt_schedule();
}
else
{
//3 获取定时器超时时间大于当前系统时间,则计算差值next_timeout,并让该线程休眠next_timeout个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);
}
}
//4 这里就不做分析,步骤和rt_timer_check()类似,只是定时器链表换成_soft_timer_list
rt_soft_timer_check();
}
}