“FreeRTOS开启任务调度”一篇说到启动任务调度最后启动Systick定时器,通过SVC中断引导第一个任务执行。然后系统就在Systick的定时中断下调度任务执行,这次介绍最后的部分,Systick和PendSV。
SysTick时钟是STM32的一个定时器,使能之后设置中断频率就会按频率触发SysTick中断。这样就可以用SysTick中断给OS切换任务提供时机。因为硬件是中断来了就会把CPU当前任务压栈,会执行中断处理函数,中断处理完成后会出栈之前数据。继续执行之前被中断的任务。在没OS情况下都是在MSP执行。既然需要OS调度,那么就要使能control寄存器,让CPU在非中断时候使用PSP栈指针。在中断MSP逻辑控制执行的PSP。所以OS调度离不开中断,同时用到双堆栈MSP和PSP隔离任务和OS。即内核空间和用户空间,OS调度在中断执行,运行在内核空间。任务在非中断执行,运行在用户空间,任务通过SVC中断调用内核。
如果SysTick不设置最低优先级,在SysTick里切换任务还会导致硬件异常。 因为在Cortex-M3中,如果OS在某个中断活跃时,抢占了该中断,而且又发生了任务调度,并执行了任务,切换到了线程运行模式,将直接触发Fault异常。(所以把SysTick和PendSV中断优先级设置最低也是有原因的,就是为了OS调度不抢占其他中断导致Fault的问题)
按理SysTick设置最低优先级就可以切换任务上下文了。一般OS在调度任务时,会关闭中断,也就是进入临界区,而OS任务调度是要耗时的,这就会出现一种情况:在任务调度期间,如果新的外部IRQ发生,CPU将不能够快速响应处理。比如串口在发数据,在OS进入临界区屏蔽中断执行任务切换期间就可能丢失读取串口发来的数据。(这是在SysTick执行任务切换带来的降低其他中断处理速度问题)
所以为了解决SysTick切换任务导致IRQ处理速度降低问题,就利用PendSV【缓期执行】的特点。滴答定时器中断,只做业务调度前的判断工作,不做任务切换。触发PendSV,PendSV并不会立即执行,因为PendSV的优先级最低,如果此时正好有IRQ请求,那么先响应IRQ,最后等到所有优先级高于PendSV的IRQ都执行完毕,再执行PendSV,进行任务调度(这样就解决了IRQ性能降低的问题)
这样也不是没缺点的,因为SysTick设置最低,如果其他中断特别频繁,就会导致SysTick执行频繁挂起,导致时钟不准,任务调度慢。因为SysTick自身执行机会被抢的很少,但是这些问题影响面已经很小了。
那如果把SysTick优先级设置最高,PendSV优先级设置最低是不是可以完美解决呢。因为SysTick的优先级最高,而且又是周期性的触发,会导致经常抢占外部IRQ,这就会导致外部IRQ响应变慢,这在一些对实时性要求高的,比如按键、断电中断等待,是不能接受的,你肯定不希望你的按键扫描体验卡顿。(SysTick又频繁抢占其他IRQ)
所以没有完美的方法,一般把SysTick和PendSV设置最低满足大部分情况,毕竟外部IRQ那么多的情况不多,一般不会影响SysTick的周期性。如果外部IRQ很多,那么可以考虑提高SysTick优先级,PendSV一直设置最低,来适当解决OS调度SysTick被抢占太厉害的问题。
设置SysTick和PendSV中断的资料来源如下图:
BaseType_t xPortStartScheduler(void)
{
portNVIC_SHPR3_REG |= portNVIC_PENDSV_PRI;
portNVIC_SHPR3_REG |= portNVIC_SYSTICK_PRI;
vPortSetupTimerInterrupt();
uxCriticalNesting = 0;
prvStartFirstTask();
return 0;
}
要点: 1.SysTick和PendSV一般设置最低优先级,来保证及时处理外部中断,因为是嵌入式实时系统对不。 2.SysTick可以按实际适当提高优先级,比重要中断低,比不重要中断高。PendSV还是以最低优先级执行。 3.SysTick在最低优先级是能切换任务的,只是会多执行切换逻辑,导致IRQ处理性能降低。引入PendSV不是必须的,主要利用PendSV缓期执行的特点提高IRQ性能。
SysTick逻辑如下,就是判断是否要切换任务,要切换的话就触发PendSV完事
void xPortSysTickHandler(void)
{
vPortRaiseBASEPRI();
{
if (xTaskIncrementTick() != pdFALSE)
{
portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT;
}
}
vPortClearBASEPRIFromISR();
}
PendSV逻辑如下,就是压栈上下文,切换pxCurrentTCB后恢复新任务上下文,然后退出中断到PSP执行新任务。
__asm void xPortPendSVHandler(void)
{
extern uxCriticalNesting;
extern pxCurrentTCB;
extern vTaskSwitchContext;
PRESERVE8
mrs r0, psp
isb
ldr r3, =pxCurrentTCB
ldr r2, [r3]
stmdb r0 !, { r4 - r11 }
str r0, [r2]
stmdb sp !, { r3, r14 }
mov r0, #configMAX_SYSCALL_INTERRUPT_PRIORITY
msr basepri, r0
dsb
isb
bl vTaskSwitchContext
mov r0, #0
msr basepri, r0
ldmia sp !, { r3, r14 }
ldr r1, [r3]
ldr r0, [r1]
ldmia r0 !, { r4 - r11 }
msr psp, r0
isb
bx r14
nop
}
这就是我对SysTick和PendSV的理解。调度策略也是看系统定位是看重实时性还是均衡调度。及按照IRQ的数量和不同中断类型重要决定SysTick优先级,如果不能评估,那么最低优先级本来就是不错的选择。
所以方案没有完美的方案,就看关注什么了而已。注重OS调度性能的话那么就会占用其他IRQ的处理时间,可能导致重要中断超时(比如导弹飞过头了)。重视IRQ处理那么可能会因为频繁的IRQ导致OS调度很缓慢。适合自己的就是最好的。
|