文章目录
PendSV异常
PendSV翻译为可挂起系统中断,从名字上可以看出,这个系统中断可以被挂起,等到时机成熟的时候再去执行,一般会把它的优先级设置为最低,它对OS操作系统的任务切换时非常重要的,是OS设计的关键。可以通过中断控制和状态寄存器ICSR的bit28,也就是PendSV的挂起位置1,就会触发一次PendSV中断。与SVC异常不同,它是不精确的,因此它可以再更高优先级的异常中来被触发,触发后要等到当前的高优先级异常处理完之后再去执行。
(后面会详细讲一下中断和NVIC)
1. 没有PendSV异常的任务切换
首先说一下能够引起上下文切换的情况有两种:(1)执行一次系统调用,例如调用taskYIELD_IF_USING_PREEMPTION()->抢占式调度;(2)系统滴答定时器(systick)中断。
当没有PendSV时,systick引起上下文切换的方式如下:
在每一次systick中断来的时候都会进行一次上下文切换,这种情况并没有考虑其他中断的情况,当在systick来之前有其他中断来了,其他中断执行到一半的时候,systick异常来了,此时具体情况如下:
当systick打断IRQ再回到任务中的时候就会触发fault,引起系统的异常。
2. 有PendSV异常的任务切换
当有PendSV异常的时候,具体的过程如下:
FreeRTOS系统的任务切换最终都是在PendSV中断服务函数中完成的,ucos也是在PendSV中断中完成任务切换的。因为这个原因,PendSV异常触发的条件和上面说的触发任务切换的条件一样。
2.1 系统调用引起的任务切换
当有更高优先级任务来的时候抢占式调度会暂停当前的任务去执行新来的更高优先级的任务,即调用taskYIELD_IF_USING_PREEMPTION()宏,具体的代码如下:
// Tasks.c
#if( configUSE_PREEMPTION == 0 )
/* If the cooperative scheduler is being used then a yield should not be
performed just because a higher priority task has been woken. */
#define taskYIELD_IF_USING_PREEMPTION()
#else
#define taskYIELD_IF_USING_PREEMPTION() portYIELD_WITHIN_API()
#endif
// FreeRTOS.h
#ifndef portYIELD_WITHIN_API
#define portYIELD_WITHIN_API portYIELD
#endif
// Portmacro.h
/* Scheduler utilities. */
/* 任务切换其实就是挂起了个PendSV异常 */
#define portYIELD() \
{ \
/* Set a PendSV to request a context switch. */ \
portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT; \ // 中断控制和状态寄存器的Bit28位置1,启动PendSV异常
\
/* Barriers are normally not required but do ensure the code is completely \
within the specified behaviour for the architecture. */ \
__dsb( portSY_FULL_READ_WRITE ); \
__isb( portSY_FULL_READ_WRITE ); \ // 内存屏障其实没有,为了保证代码的统一性
}
#define portEND_SWITCHING_ISR( xSwitchRequired ) if( xSwitchRequired != pdFALSE ) portYIELD()
#define portYIELD_FROM_ISR( x ) portEND_SWITCHING_ISR( x ) // 中断中的任务切换同样调用portYI