内核入门(六)——时钟管理

一 时钟节拍

  任何操作系统都需要提供一个时钟节拍,以供系统处理所有和时间有关的事件,如线程的延时、线程的时间片轮转调度以及定时器超时等。时钟节拍是特定的周期性中断,这个中断可以看做是系统心跳,中断之间的时间间隔取决于不同的应用,一般是1ms–100ms,时钟节拍率越快,系统的额外开销就越大,从系统启动开始计数的时钟节拍数称为系统时间。
  RT-Thread 中,时钟节拍的长度可以根据RT_TICK_PER_SECOND的定义来调整,等于1/RT_TICK_PER_SECOND 秒。

1.1 实现方式

  时钟节拍由配置为中断触发模式的硬件定时器产生, 当中断到来时, 将调用一次:void rt_tick_increase(void),通知操作系统已经过去一个系统时钟。下面的中断函数以STM32 定时器作为示例。

void SysTick_Handler(void)
{
    
    
/* 进入中断*/
rt_interrupt_enter();
… …
rt_tick_increase();		//对全局变量rt_tick进行自加
/* 退出中断*/
rt_interrupt_leave();
}

  其中rt_tick_increase()的代码如下。可以看到全局变量rt_tick在每经过一个时钟节拍时,值就会加1,rt_tick的值表示了系统从启动开始总共经过的时钟节拍数,即系统时间。此外,每经过一个时钟节拍时,都会检查当前线程的时间片是否用完,以及是否有定时器超时。rt_timer_check()用于检查系统硬件定时器链表,如果有定时器超时,将调用相应的超时函数。且所有定时器在定时超时后都会从定时器链表中被移除,而周期性定时器会在它再次启动时被加入定时器链表。

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();
}

1.2 获取时钟节拍

  调用rt_tick_get(void)会返回当前rt_tick的值,即可以获取到当前的时钟节拍值。此接口可用于记录系统的运行时间长短,或者测量某任务运行的时间。接口函数如下:

rt_tick_t rt_tick_get(void);

二 定时器管理

  定时器是指从某特定的时刻开始,经过一定的时间之后,触发另一件事情,可以分为硬件定时器、软件定时器:

  • 硬件定时器
    硬件定时器是芯片本身提供的定时功能。一般是由外部晶振提供给芯片输入时钟,芯片向软件模块提供一组配置寄存器,接受控制输入,到达设定时间值后芯片中断控制器产生时钟中断。硬件定时器的精度一般很高,可以达到纳秒级别,并且是中断触发方式。

  • 软件定时器
    软件定时器是由操作系统提供的一类系统接口,构建在硬件定时器基础之上,使系统能够提供不受数目限制的定时器服务。

  RT-Thread操作系统提供软件实现的定时器,以时钟节拍(OS Tick)的时间长度为单位,即定时数值是OS Tick的整数倍;如果有高精度定时需求(间隔低于OS Tick),则需要使用系统硬件定时器。

2.1 基础介绍

  RT-Thread 的定时器按照工作机制可以分为以下两类:

  • 单次触发
    单次定时器在启动后只会触发一次定时器事件,然后定时器自动停止。
  • 周期触发
    周期定时器会周期性的触发定时器事件,直到用户手动的停止,否则将永远持续执行下去。

  另外,根据超时函数执行时所处的上下文环境,RT-Thread 的定时器又可以分为以下两种模式:

  • HART_TIMER

  HARD_TIMER模式的定时器超时函数在中断上下文环境中执行,可以在初始化/ 创建定时器时使用参数RT_TIMER_FLAG_HARD_TIMER来指定。
  在中断上下文环境中执行时,对于超时函数的要求与中断服务例程的要求相同:执行时间应该尽量短,执行时不应导致当前上下文挂起、等待。例如在中断上下文中执行的超时函数它不应该试图去申请动态内存、释放动态内存等。
  RT-Thread 定时器默认的方式是HARD_TIMER模式,即定时器超时后,超时函数是在系统时钟中断的上下文环境中运行的。在中断上下文中的执行方式决定了定时器的超时函数不应该调用任何会让当前上下文挂起的系统函数;也不能够执行非常长的时间,否则会导致其他中断的响应时间加长或抢占了其他线程执行的时间。

  • SOFT_TIMER

  通过宏定义RT_USING_TIMER_SOFT来决定是否启用该模式。该模式被启用后,系统会在初始化时创建一个timer 线程,然后SOFT_TIMER模式的定时器超时函数在都会在timer 线程的上下文环境中执行。可以在初始化/创建定时器时使用参数RT_TIMER_FLAG_SOFT_TIMER来指定设置SOFT_TIMER模式。

2.2 工作机制

  RT-Thread 定时器模块中维护着两个重要的全局变量:

  1. rt_tick
    当前系统经过的时间:当硬件定时器中断触发时rt_tick加1;
  2. rt_timer_list
    系统新创建并激活的定时器都会按照以超时时间排序的方式插入到
    rt_timer_list 链表中。

2.2.1 定时器控制块

  定时器控制块由结构体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链表中。

2.2.2 定时器跳表算法

  系统新创建并激活的定时器都会按照以超时时间排序的方式插入到rt_timer_list链表中,也就是说这是一个有序链表,RT-Thread中使用了跳表算法来加快搜索链表元素的速度。
  跳表是一种基于并联链表的数据结构,实现简单,插入、删除、查找的时间复杂度均为O(log n)。跳表是在链表的基础上增加了“跳跃” 功能,使得在查找元素时,跳表能够提供O(log n) 的时间复杂度。即有序链表的插入算法。

2.3 管理方式

  在系统启动时需要初始化定时器管理系统,通过下面的函数接口完成:

void rt_system_timer_init(void);

  如果需要使用SOFT_TIMER,则系统初始化时,应该调用:

void rt_system_timer_thread_init(void);

  对定时器的操作方式有:

2.3.1 创建和删除

  动态创建:内核首先从动态内存堆中分配一个定时器控制块,然后对该控制块进行基本的初始化。

rt_timer_t rt_timer_create(const char* name,
					void (*timeout)(void* parameter),
					void* parameter,
					rt_tick_t time,
					rt_uint8_t flag);

  删除:系统会把这个定时器从rt_timer_list 链表中删除,然后释放相应的定时器控制块占有的内存。

rt_err_t rt_timer_delete(rt_timer_t timer);

2.3.2 初始化和剥离

  初始化:静态初始化相应的定时器控制块,初始化相应的定时器名称,定时器超时函数等。

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);

2.3.3 启动和停止

  启动定时器:调用定时器启动函数接口后,定时器的状态将更改为激活状态RT_TIMER_FLAG_ACTIVATED,并按照超时顺序插入到rt_timer_list队列链表中,此时定时器才会被激活。

rt_err_t rt_timer_start(rt_timer_t timer);

  停止定时器:调用定时器停止函数接口后,定时器状态将更改为停止状态,并从rt_timer_list链表中脱离出来不参与定时器超时检查。

rt_err_t rt_timer_stop(rt_timer_t timer);

2.3.4 控制定时器

  除了上述提供的一些编程接口,RT-Thread 也额外提供了定时器控制函数接口,以获取或设置更多定时器的信息。控制定时器函数接口如下:

rt_err_t rt_timer_control(rt_timer_t timer, rt_uint8_t cmd, void* arg);

//timer:定时器句柄,指向要停止的定时器控制块
//cmd:用于控制定时器的命令,当前支持四个命令,分别是设置定时时间,查看定时时间,设置单次触发,设置周期触发
//arg:与cmd 相对应的控制命令参数

2.4 高精度延时

  RT-Thread 定时器的最小精度是由系统时钟节拍所决定,当需要的定时精度超过时钟节拍,只能通过读取系统某个硬件定时器的计数器或直接使用硬件定时器的方式。

猜你喜欢

转载自blog.csdn.net/qq_33604695/article/details/105547077