一、临界段的概念
所谓的临界段就是在执行时不能被中断的代码段。在FreeRTOS中,临界段最常出现的地方就是对全局变量的操作。那么什么情况下临界段会被打断?一个是系统调度,还有一个是外部中断。在FreeRTOS中,系统调度最终也是产生PendSV中断,在PendSV Handler中实现任务的切换,所以还是可以归结为中断。既然这样,FreeRTOS对临界段的保护最终还是回归到对中断的开和关的控制。
二、Cortex-M内核快速关中断指令
为了快速地开关中断,Cortex-M内核专门设置了一条CPS指令,它有四种用法,具体如下:
CPSID I;PRIMASK = 1;关中断
CPSIE I;PRIMASK = 0;开中断
CPSID F;FAULTMASK = 1;关异常
CPSIE F;FAULTMASK = 1;开异常
在FreeRTOS中,对中断的开关是通过BASEPRI寄存器来实现的,即大于等于BASEPRI寄存器的值的中断会被屏蔽,小于BASEPRI的值的中断不会被屏蔽,不受FreeRTOS管理。用户可以设置BASEPRI的值来选择性地给一些非常紧急的中断留出余地。《Cortex M3与M4权威指南》中对BASEPRI寄存器的描述如下:
In some cases, you might only want to disable interrupts with priority lower than a certain level. In this case, you could use the BASEPRI register. To do this, simply write the required masking priority level to the BASEPRI register. For example, if you want to block all exceptions with priority level equal to or lower than 0x60,you can write the value to BASEPRI:
__set_BASEPRI(0x60); // Disable interrupts with priority 0x60-0xFF using CMSIS-Core function
For users of assembly language, the same operation can be written as:
MOVS R0, #0x60
**MSR BASEPRI, R0 **; Disable interrupts with priority
三、关中断
FreeRTOS中关中断的函数在portmacro.h文件中实现,分为不带返回值和带返回值两种。
1.不带返回值的关中断函数
#define portDISABLE_INTERRUPTS() vPortRaiseBASEPRI()
static portFORCE_INLINE void vPortRaiseBASEPRI(void)
{
uint32_t ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY;
__asm
{
msr basepri,ulNewBASEPRI
dsb
isb
}
}
不带返回值的意思是在向BASEPRI中写入新值时,不用先将BASEPRI的值保存起来,即不用考虑当前的中断状态是怎么样的,这意味着这个函数不能在中断中使用。第6行的configMAX_SYSCALL_INTERRUPT_PRIORITY是在FreeRTOSConfig.h中定义的一个宏,该宏默认是191,高四位有效即等于0xb0,即十进制的11,所以优先级大于等于11的中断都会被屏蔽,11以内的中断不受FreeRTOS管理。
2.带返回值的中断
#define portSET_INTERRUPT_MASK_FROM_ISR() ulPortRaiseBASEPRI()
static portFORCE_INLINE uint32_t ulPortRaiseBASEPRI(void)
{
uint32_t ulReturn,ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY;
__asm
{
mrs ulReturn,basepri
msr basepri,ulNewBASEPRI
dsb
isb
}
return ulReturn;
}
带返回值的意思是在向BASEPRI写入新值之前,先将BASEPRI的值保存起来,在更新完BASEPRI的值时,将之前的BASEPRI的值返回,返回的值作为参数传入开中断函数中,以此来恢复之前的状态。
四、开中断
开中断函数在portmacro.h中定义。
#define portENABLE_INTERRUPTS() vPortSetBASEPRI(0)
#define portCLEAR_INTERRUPT_MASK_FROM_ISR(x) vPortSetBASEPRI(x)
static portFORCE_INLINE void vPortSetBASEPRI(uint32_t ulBASEPRI)
{
__asm
{
msr basepri,ulBASEPRI
}
}
第2行为不带中断保护的开中断函数,直接将BASEPRI的值设置为0,与不带返回值的关中断函数portDISABLE_INTERRUPTS()成对使用。第4行是带中断保护的开中断函数,将上一次关中断时返回的值作为形参重新写入BASEPRI寄存器中,与带返回值的关中断函数portSET_INTERRUPT_MASK_FROM_ISR()成对使用。
五、进入、退出临界段的宏
进入和退出临界段的宏在task.h中定义,具体如下:
#define taskENTER_CRITICAL() portENTER_CRITICAL()
#define taskENTER_CRITICAL_FROM_ISR() portSET_INTERRUPT_MASK_FROM_ISR()
#define taskEXIT_CRITICAL() portEXIT_CRITICAL()
#define taskEXIT_CRITICAL_FROM_ISR(x) portCLEAR_INTERRUPT_MASK_FROM_ISR(x)
进入和退出临界段的宏分为中断保护和非中断保护两种,但最终都是通过开关中断来实现的。这样多次定义的目的是为了兼容不同的处理器,因为不同的处理器的开关中断的实现是不同的,在task.h中的最终调用的,在port.h中的是根据不同的处理器来实现的。
1.进入临界段
(1)不带中断保护,不能嵌套
#define portENTER_CRITICAL() vPortEnterCritical()
void vPortEnterCritical(void)
{
portDISABLE_INTERRUPTS();
uxCriticalNesting++;
if(uxCriticalNesting==1)
{
configASSERT((portNVIC_INT_CTRL_REG & portVECTACTIVE_MASK) == 0);
}
}
第7行中的uxCriticalNesting是一个port.c中定义的静态变量,默认初始化为0xaaaaaaaa,在调度器启动后会被重新初始化为0,其作用是对临界段嵌套的计数。第8行,如果uxCriticalNesting等于表示当前有一层嵌套,要确保当前没有中断活跃,即啮合外设SCB中的中断和控制寄存器ICSR的低8位要等于0。
(2)带中断保护,可以嵌套
#define portSET_INTERRUPT_MASK_FROM_ISR() ulPortRaiseBASEPRI()
定义的函数在前面关中断的时候已经提及。
2.退出临界段
(1)不带中断保护,不能嵌套
#define portEXIT_CRITICAL() vPortExitCritical()
void vPortExitCritical(void)
{
configASSERT(uxCriticalNesting);
uxCriticalNesting--;
if(uxCriticalNesting == 0)
{
portENABLE_INTERRUPTS();
}
}
(2)带中断保护,可以嵌套
#define portCLEAR_INTERRUPT_MASK_FROM_ISR(x) vPortSetBASEPRI(x)
六、临界段代码的应用
在FreeRTOS中,临界代码的应用出现在两种场合,一种是在中断场合,另一种是在非中断场合。应用示例代码如下:
{
uint32_t ulReturn;
ulReturn = taskENTER_CRITICAL_FROM_ISR();
taskEXIT_CRITICAL_FROM_ISR();
}
{
taskENTER_CRITICAL();
taskEXIT_CRITICAL();
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)