提兵百万西湖上,立马吴山第一峰!这篇文章主要讲述RT-Thread快速入门-时钟管理相关的知识,希望能为你提供帮助。
时钟节拍
任何操作系统都需要提供一个时钟节拍,以供系统处理所有和时间有关的事件,如延时、线程的时间片轮转调度以及定时器超时等。时钟节拍(OS Tick)是操作系统中最小的时间单位。
时钟节拍是特定的周期性中断,这个中断之间的时间间隔取决于具体的应用,一般是 1-100ms。时钟节拍率越快,系统的额外开销就越大。
RT-Thread 中,一个时钟节拍的时长根据 rtconfig.h 配置文件中, RT_TICK_PER_SECOND
的 定 义 来 调 整, 等 于 1/RT_TICK_PER_SECOND
秒 。
时钟节拍的实现
时钟节拍由配置为中断触发模式的硬件定时器产 生,在中断服务程序中调用如下函数,通知操作系统已经过去一个系统时钟:
void rt_tick_increase(void)struct rt_thread *thread;
/* 全局 rt_tick 递增 */
#ifdef RT_USING_SMP
rt_cpu_self()->
tick ++;
#else
++ rt_tick;
#endif/* 检查时间片 */
thread = rt_thread_self();
-- thread->
remaining_tick;
if (thread->
remaining_tick == 0)/* 重新赋初值 */
thread->
remaining_tick = thread->
init_tick;
/* 线程挂起 */
thread->
stat |= RT_THREAD_STAT_YIELD;
/* yield */
rt_thread_yield();
/* 检查定时器 */
rt_timer_check();
从源代码中可以看出,每经过一个时钟节拍,全局变量
rt_tick
的值就会加 1。然后检查当前线程的时间片是否用完,以及是否有定时器超时。如果当前线程的时间片用完,则进行同优先级线程之间的切换。不同的硬件定时器中断实现都不同,以 STM32 定时器中断为例:
void SysTick_Handler(void)/* 进 入 中 断 */
rt_interrupt_enter();
……
rt_tick_increase();
/* 退 出 中 断 */
rt_interrupt_leave();
在中断函数中,调用
rt_tick_increase()
对全局变量 rt_tcik
加 1。rt_tick
的值表示了系统从启动到现在共经过的时钟节拍个数。定时器工作机制
RT-Thread 提供的定时器基于系统的节拍,提供了基于节拍整数倍的定时能力,即定时器定时以时钟节拍为单位。如此,定时器定时长短是
OS Tick
时长的整数倍。如果一个时钟节拍是 10ms,那么系统软件定时器时长只能是 10ms、20ms、100等,而不能是 15ms。
定时器介绍
RT-Thread 提供了两种类型的定时器:
- 单次触发定时器。这类定时器触发一次定时器事件后,会自动停止。
- 周期触发定时器。这类定时器会周期性地触发定时器事件,直到用户手动停止。
HARD_TIMER
模式,超时函数在中断上下文环境中执行。SOFT_TIMER
模式,在系统创建的定时器线程上下文环境中执行。
HARD_TIMER
模式的定时器这种模式是 RT-Thread 定时器默认的工作方式,定时器超时后,超时函数在系统时钟中断的上下文环境中执行。
这种情况下,对于超时函数的要求与中断服务例程的要求相同:执行时间应该尽量短、执行时不应该导致当前线程挂起等。否则会导致其他中断的响应时间加长,或抢占了其他线程执行的时间。
SOFT_TIMER
模式的定时器这种工作模式,需要通过宏定义
RT_USING_TIMER_SOFT
来决定是否启用。启用这个模式后,RT-Thread 会在初始化时创建一个 timer 线程,SOFT_TIMER
模式的定时器超时函数都会在 timer 线中执行。定时器如何工作
RT-Thread 维护着两个重要的全局变量:
rt_tick
, 当前系统经过的时钟节拍个数。rt_timer_list
, 定时器链表。创建并激活的定时器都会按照超时时间从小到大进行排序,插入到这个链表中。
这三个定时器分别加上系统当前时间
rt_tick
, 从小到大排序链接在 rt_timer_list
中:文章图片
rt_tick
随着硬件定时器的触发一直在增长,50 个节拍后,rt_tick
从 20 增长到 70,与 Timer1 的 timerout
值相同,这时会触发 Timer1 定时器关联的超时函数,同时将其从 rt_timer_list
链表上删除。同理,100 个节拍和 500 个节拍过去后,Timer2 和 Timer3 定时器的超时函数会被触发执行,将定时器 Timer2 和 Timer3 从
rt_timer_list
中删除。定时器控制块
定时器控制块是 RT-Thread 用于管理定时器的一个数据结构,由结构体
struct rt_timer
定义形成定时器内核对象,再链接到内核容器中进行管理。定时器控制块会存储定时器的一些信息,例如初始时钟节拍数、超时到达的节拍数、定时器之间连接用的链表结构、超时回调函数等。具体定义如下:
struct rt_timerstruct 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;
定时器管理
前面介绍了定时器相关的理论知识,那么 RT-Thread 提供了怎样的定时器操作函数,以及如何使用它们呢?
RT-Thread 提供的定时器相关的操作包括:
- 创建/初始化定时器
- 启动定时器
- 控制定时器
- 删除/脱离定时器
文章图片
1. 创建定时器
创建一个定时器有两种方式:动态创建和静态初始化。
动态创建一个定时器,使用如下函数接口:
rt_timer_t rt_timer_create(const char *name,
void (*timeout)(void *parameter),
void*parameter,
rt_tick_ttime,
rt_uint8_tflag)
调用此函数后,内核自动从内存堆中分配一个定时器控制块,然后初始化该定时器控制块。各个参数说明如下:
参数 | 描述 |
---|---|
name | 定时器名称 |
timeout | 定时器超时函数指针 |
parameter | 定时器超时函数的入口参数 |
time | 定时器超时时间,单位是时钟节拍 |
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/* 软 件 定 时 器 */
上面两组可以以 " 或" 逻辑方式赋值给
flag
。静态创建一个定时器,需要用户定义一个定时器控制块结构体
struct rt_timer
变量,然后 rt_timer_init()
函数对其初始化。该函数原型如下: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_timer_create()
多了一个参数 timer
,其他参数都相同,不再赘述。参数 timer
实际上是定时器控制块指针。2. 启动定时器
定时器创建之后,不会被立即启动,需要在调用启动定时器函数接口后,才开始工作。
RT-Thread 提供的启动定时器函数如下:
rt_err_t rt_timer_start(rt_timer_t timer);
函数的参数
timer
为定时器控制块指针(定时器句柄),指向要启动的定时器控制块。调用启动函数后,定时器的状态更改为激活状态,并按照超时时间顺序插入到
rt_timer_list
队列链表中。启动定时器后,如果想停止它,可以用下面的函数:
rt_err_t rt_timer_stop(rt_timer_t timer);
调用该函数后,定时器状态更改为停止,并从
rt_timer_list
链表中脱离出来,不参与定时器超时检查。函数返回 RT_EOK,表示成功停止定时器。返回 -RT_ERROR,说明定时器已经处于停止状态了。
定时器应用演示
理论+实践是学习新知识最有效的方法。
举例来演示如何创建定时器。这个例程动态创建两个定时器,一个单次定时器,一个周期定时器,并让定时器运行一段时间后停止。代码如下:
#include <
rtthread.h>
/* 定时器的控制块 */
static rt_timer_t timer1;
static rt_timer_t timer2;
static int cnt = 0;
/* 定时器1超时函数 */
static void timeout1(void *parameter)rt_kprintf("periodic timer is timeout %d\\n", cnt);
/* 运行第 10 次,停止周期定时器 */
if (cnt++>
= 9)rt_timer_stop(timer1);
rt_kprintf("periodic timer was stopped! \\n");
/* 定时器 2 超时函数 */
static void timeout2(void *parameter)rt_kprintf("one shot timer is timeout\\n");
int main()/* 创建定时器1周期定时器 */
timer1 = rt_timer_create("timer1", timeout1,
RT_NULL, 10,
RT_TIMER_FLAG_PERIODIC);
/* 启动定时器1 */
if (timer1 != RT_NULL) rt_timer_start(timer1);
/* 创建定时器2单次定时器 */
timer2 = rt_timer_create("timer2", timeout2,
RT_NULL, 30,
RT_TIMER_FLAG_ONE_SHOT);
/* 启动定时器2 */
if (timer2 != RT_NULL) rt_timer_start(timer2);
return 0;
编译运行结果如下:
文章图片
周期性定时器 1 的超时函数,每 10 节拍运行 1 次,共运行 10 次,之后停止(调用
rt_timer_stop()
)。单次定时器 2 的超时函数在 30 个时钟节拍后运行一次。
下面举例说明静态创建定时器,需要定义定时器控制块结构变量,然后调用初始化函数对其初始化:
#include <
rtthread.h>
/* 定时器的控制块 */
static struct rt_timer timer1;
static struct rt_timer timer2;
static int cnt = 0;
/* 定时器1超时函数 */
static void timeout1(void* parameter)rt_kprintf("periodic timer is timeout\\n");
/* 运行10次 */
if (cnt++>
= 9)rt_timer_stop(&
timer1);
/* 定 时 器 2 超 时 函 数 */
static void timeout2(void* parameter)rt_kprintf("one shot timer is timeout\\n");
int main(void)/* 初始化定时器1 */
rt_timer_init(&
timer1, "timer1", /* 定 时 器 名 字 是 timer1 */
timeout1, RT_NULL, 10,
RT_TIMER_FLAG_PERIODIC);
/* 周期定时器 */
/* 初始化定时器2 */
rt_timer_init(&
timer2, "timer2", /* 定 时 器 名 字 是 timer2 */
timeout2, RT_NULL, 30,
RT_TIMER_FLAG_ONE_SHOT);
/* 单次定时器 *//* 启动定时器 */
rt_timer_start(&
timer1);
rt_timer_start(&
timer2);
return 0;
其执行结果与动态创建示例相同。
其他定时器管理函数
初学者掌握定时器创建使用即可,RT-Thread 还提供了其他的定时器管理函数,可以了解学习。
1. 删除定时器
动态创建的定时器,可以用下面的函数删除:
rt_err_t rt_timer_delete(rt_timer_t timer);
调用这个函数接口后,系统会把这个定时器从 rt_timer_list 链表中删除,然后释放相应的定时器控制块占有的内存。
静态创建的定时器,可以用下边的函数脱离定时器:
rt_err_t rt_timer_detach(rt_timer_t timer);
脱离定时器时,系统会把定时器对象从内核对象容器中脱离,但是定时器对象所占有的内存不会被释放。
2. 控制定时器
RT-Thread 也额外提供了定时器控制函数接口,以获取或设置更多定时器的信息。控制定时器函数接口如下:
rt_err_t rt_timer_control(rt_timer_t timer, rt_uint8_t cmd, void* arg);
控制定时器函数接口可根据命令类型参数,来查看或改变定时器的设置。
参数
cmd
为用于控制定时器的命令,当前支持四个命令:设置定时时间、查看定时时间、设置单次触发、设置周期触发。#define RT_TIMER_CTRL_SET_TIME0x0/* 设置定时器超时时间 */
#define RT_TIMER_CTRL_GET_TIME0x1/* 获得定时器超时时间 */
#define RT_TIMER_CTRL_SET_ONESHOT0x2/* 设置定时器为单次定时器 */
#define RT_TIMER_CTRL_SET_PERIODIC0x3/* 设置定时器为周期型定时器 */
arg
为控制命令的参数。【RT-Thread快速入门-时钟管理】OK,今天先到这,下次继续。加油~
推荐阅读
- OpenHarmony兴趣组SIG非程序员看了不亏?上手OpenBlock
- (服务注册名称的来历)
- 信息安全无小事!手把手教你日志脱敏
- win8系统语音识别技巧的运用办法
- 图文 Win8系统修好停止工作弹窗的办法
- 图文 如何让Win8系统打开窗口总是显示菜单栏
- 图文 Win 8系统屏幕亮度无法调节的处理办法
- 图文 Win8系统碎片整理优化图文详细教程
- 图文 Win8系统启用与关闭telnet组件的图文详细教程