参考资料:RTT官网文档、《cortex-M3权威指南》
关键字:分析RT-Thread源码、stm32、RTOS、时钟管理。
简介
完成了调度器,对象管理,线程管理后,我们就可以多任务并行执行了,但是还是有很多问题,例如我们同优先级的线程,在不挂起的情况下,只能通过手动yield放弃CPU,另一个线程才有机会运行。低优先级的线程也得不到获取CPU资源的机会,这样我们的多任务功能就大打折扣。同时我们的线程也无法做一些延时操作,不能做一些定时任务。所以,为了确保没有任务能霸占系统,以及为系统提供各种定时功能,我们需要一个时钟管理。
首先,时钟管理需要哪些功能:
1,要有一个定期性的中断或者异常来作为系统的时基 --时钟节拍
2,通过时钟节拍来推动我们的任务管理(调度) --管理时间片
3,通过时钟节拍来实现延时功能 --延时功能
4,通过时钟节拍来实现定时功能 --定时功能
可以看出,时钟节拍是我们实现这些功能的核心。
时钟节拍
任何操作系统都需要提供一个时钟节拍,以供系统处理所有和时间有关的事件,如线程的延时、线程的时间片轮转调度以及定时器超时等。时钟节拍是特定的周期性中断,这个中断可以看做是系统心跳,中断之间的时间间隔取决于不同的应用,一般是 1ms–100ms,时钟节拍率越快,系统的额外开销就越大,从系统启动开始计数的时钟节拍数称为系统时间。
RT-Thread 中,时钟节拍的长度可以根据 RT_TICK_PER_SECOND 的定义来调整,等于 1/RT_TICK_PER_SECOND 秒。RT_TICK_PER_SECOND默认是100,即一个时钟节拍为10ms。
SysTick定时器
在以前,大多操作系统需要一个硬件定时器来产生操作系统需要的滴答中断,作为整个系统的时基。现在,cortex-M3处理器内部就包含了一个专门用来处理系统滴答的定时器–SysTick。
CM3为它专门开出一个异常类型,SYSTICK异常(异常号:15)。在上下文切换的时候,CM3也专门提供了一个PendSV异常,是不是很贴心。下面介绍一下SysTick这个定时器。
SysTick 是一个24 位的倒计数定时器,当计到0 时,将从RELOAD 寄存器中自动重装载定时初值。只要不把它在SysTick 控制及状态寄存器中的使能位清除,就永不停息。
详细请看一下CM3权威指南第8章。
源码分析
下面我们来看SysTick在RTT中的具体操作。
在 rt_hw_board_init 初始化了SysTick。
void rt_hw_board_init(void)
{
...
/* Configure the SysTick */
SysTick_Config( SystemCoreClock / RT_TICK_PER_SECOND );
...
}
SystemCoreClock = 72000 000,因为我的是103的,频率是72M。RT_TICK_PER_SECOND默认是100,所以这里将触发异常改为了10ms触发。
异常处理函数:
void SysTick_Handler(void)
{
rt_tick_increase();
}
void rt_tick_increase(void)
{
struct rt_thread *thread;
/* increase the global tick */
++ rt_tick;
/* check time slice */
thread = rt_thread_self();
-- thread->remaining_tick;
if (thread->remaining_tick == 0)
{
/* change to initialized tick */
thread->remaining_tick = thread->init_tick;
/* yield */
rt_thread_yield();
}
/* check timer */
rt_timer_check();
}
在时钟节拍SysTick的的异常处理函数中调用了rt_tick_increase,rt_tick_increase里维护了一个系统节拍数的变量rt_tick,然后检查当前线程是否已经达到规定的时间片,如果是则调用rt_thread_yield让出cpu控制权。接着是定时器检测,这个我们后面再详细讲,暂时先不管。
测试
接下来我们就将系统时间节拍和时间片加入我们的RTT-Mini中。
void clock_systick_handler(void)
{
++systick;
--current_thread->remaining_tick;
if (current_thread->remaining_tick == 0) {
current_thread->remaining_tick = current_thread->tick;
thread_yield();
}
}
void thread_1_entry(void *param)
{
uint32_t i = 0;
while (1) {
printf("hello. i : %ld\r\n", i++);
// thread_yield();
}
}
void thread_2_entry(void *param)
{
uint32_t i = 0;
while (1) {
printf("world. i : %ld\r\n", i++);
// thread_yield();
}
}
现在我们线程中去掉yield,两个任务也能轮流运行了。