This article was last updated on <span id="expire-date"></span> days ago, the information described in the article may be outdated.
上面说过了 uCOS-II 通过不断的在任务之间切换实现任务调度,这一个小节我们来分析 uCOS-II 到底是如何调度任务的。
首先我们要清楚,既然用户给定的任务是一个个无穷循环,所以操作系统肯定不能指望任务自己放弃 CPU 的使用权,让给其他的函数去执行;相反的,当一个任务执行一段时间之后,我们的 OS 就需要“强行的”从它手里拿到处理器的时间,这样的操作系统被称为 抢占式 的,相反那种不同任务之间 Co-operate 的,就叫做 非抢占式 的。
可我们的 OS 是怎么做到 “抢占” 的动作呢?
回到 Cortex 内核,我们发现,整个处理器的运行时可以分为两个模式:
Thread 模式
这种模式用于处理我们的后台代码,也就是无穷循环内的代码
Handler 模式
当有系统异常/外部中断发生时,处理器陷入 Handler 模式,像是进入子函数调用一样将当前的上下文入栈,并切换 MSP/PSP,之后从中断向量表中更新用户函数地址到
PC
寄存器
uCOS-II 就是使用了这样的中断模式进行任务切换的,具体来说,它使用了 SysTick 作为系统时基,并且在 SysTick 的中断处理函数内进行操作系统的任务调度,这样做的原因主要有两个:
SysTick 默认使用 HCLK 时钟
Cortex 内核的大部分组件都使用这个时钟,它是由 SYSCLK 时钟经过 AHB 预分频之后得到的,其实在 SysTick 的配置中我们还可以对这个时钟源在进行一次分频:
CLKSOURCE 配置为 1 时使用 HCLK
CLKSOURCE 配置为 0 时进行 8 分频
但是默认在 SysTick 的初始化中使用了 1 倍频,也就是 HCLK,这样操作系统就能得到一个和 Cortex 内核相同频率的稳定时钟源
SysTick 属于 Cortex 的内核设备
内核既然提供了这样一个专门用的时钟,当然没有必要再去使用其他的外设,浪费MCU的资源
正是因为使用了 SysTick 作为系统时基,所以我们需要仔细的配置它的时间间隔(也就是24位的递减寄存器),这个时间如果配置的过长会导致系统的实时性能下降;配置的过短又会不断的触发中断调度任务,浪费处理的大量资源用于切换上下文。
除了上面的基于时间片的任务调度之外,还有两种情况需要系统进行任务调度:
对于外部中断处理函数,uCOS-II 自然是没有办法追踪处理器的状态,所以用户需要手动的添加代码让 OS 能够知道我们当前在处理中断,并把当前的任务状态转移至中断态:
1
2
3OSIntEnter();
/* ISR Coding */
OSIntExit();当 ISR 执行完毕之后,调用的
OSIntExit
函数会触发一次中断级别的任务调度,具体的行为我们放在以后分析。如果任务代码中调用了类似
OSTimeDly
之类的 API 函数,则会触发一次任务级别的调度,通过OS_Sched
函数检测是否存在更高级别的已就绪任务,如果有的话则进行一次切换,这个切换的行为我们也放在之后去讲。
简单来说,uCOS-II 的系统调度分为了三个部分:
- 基于 SysTick 的系统时钟调度
- 任务内调用“阻塞型” 系统 API 进行调度
- 中断调度
通过这些调度方法,OS 可以尽可能的让具有最高优先级的任务处于运行状态之中,这也就是 RTOS 中 实时 性的保证。
Comments