前面分析了启动任务调度器的源码,在创建好空闲任务、启动滴答定时器(systick)后就调用SVC中断跳转到任务去执行,但作为OS我们一般不会只创建一个任务,都是有多任务需求才使用OS,后续任务一旦多了,就涉及了到了任务切换,这也是OS的核心,如何根据任务的优先级的和当前状态来切换任务,怎么保证每个任务都有执行的机会,这就是调度器做的工作,而调度器的核心就是任务切换,任务切换工作都在是PendSV中断中进行的,在执行一次系统调用或systick定时器中断里面定时触发一次PendSV中断就会产生一次任务上下文切换。为什么使用PendSV中断来进行任务切换,在Cortex-M3权威手册中的解释非常清晰:
PendSV(可悬起的系统调用),它和 SVC 协同使用。一方面, SVC异常是必须立即得到响应的(若因优先级不比当前正处理的高, 或是其它原因使之无法立即响应, 将上访成硬 fault——译者注), 应用程序执行 SVC 时都是希望所需的请求立即得到响应。另一方面, PendSV 则不同,它是可以像普通的中断一样被悬起的(不像 SVC 那样会上访)。 OS 可以利用它“缓期执行” 一个异常——直到其它重要的任务完成后才执行动作。 悬起 PendSV 的方法是: 手工往 NVIC 的PendSV 悬起寄存器中写 1。 悬起后, 如果优先级不够高,则将缓期等待执行。PendSV 的典型使用场合是在上下文切换时(在不同任务之间切换)。 例如, 一个系统中有两个就绪的任务,上下文切换被触发的场合可以是:
1、执行一个系统调用
2、系统滴答定时器( SYSTICK)中断,(轮转调度中需要)
让我们举个简单的例子来辅助理解。假设有这么一个系统,里面有两个就绪的任务,并且通过 SysTick 异常启动上下文切换。如下图所示。
上图是两个任务轮转调度的示意图。但若在产生 SysTick 异常时正在响应一个中断,则SysTick 异常会抢占其 ISR。在这种情况下, OS 不得执行上下文切换,否则将使中断请求被延迟,而且在真实系统中延迟时间还往往不可预知——任何有一丁点实时要求的系统都决不能容忍这种事。因此,在 CM3 中也是严禁没商量——如果 OS 在某中断活跃时尝试切入线程模式,将触犯用法 fault 异常。
为解决此问题,早期的 OS 大多会检测当前是否有中断在活跃中,只有没有任何中断需要响应时,才执行上下文切换(切换期间无法响应中断)。然而,这种方法的弊端在于,它可以把任务切换动作拖延很久(因为如果抢占了 IRQ,则本次 SysTick 在执行后不得作上下文切换,只能等待下一次 SysTick 异常),尤其是当某中断源的频率和 SysTick 异常的频率比较接近时,会发生“共振”。现在好了,PendSV 来完美解决这个问题了。PendSV 异常会自动延迟上下