一、什么是临界段
临界段就是一段在执行的时候不能被打断的代码。在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=0 ;开异常
Cortex-M内核中断屏蔽寄存器组描述:
名字 | 功能描述 |
---|---|
PRIMASK | 只有单一比特的寄存器。被置1:关掉所有可屏蔽异常,只剩下NMI和硬FAULT可以响应。被置0:没有关中断 |
FAULTMASK | 只有一个比特的寄存器。被置1:只有NMI能响应,所与其它异常(包括FAULT)统统闭嘴。被置0:没有关异常 |
BASEPRI | 这个寄存器最多9位(由表达优先级的位数决定)。它定义了被屏蔽优先级的阈值。当他被设成某个值后,所有优先级号大于此值的中断都被关(中断优先级号越大,优先级越低)。若设为0,则不关闭任何中断。 |
在FreeRTOS中,对终端的的开关是通过操作BASEPRI寄存器实现的,大于等于BASEPRI的值的中断会被关闭,小于BASEPRI的值的中断不会被关闭,不受FreeRTOS管理。
三、开关中断
1、关中断
FreeRTOS关中断函数在portmacro.h中定义,分为不带返回值和带反悔两种:
/* 不带返回值的关中断函数,不能嵌套,不能在中断里面使用 */ (1)
#define portDISABLE_INTERRUPTS() vPortRaiseBASEPRI()
void vPortRaiseBASEPRI( void )
{
uint32_t ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY; (1)-①
__asm
{
msr basepri, ulNewBASEPRI (1)-②
dsb
isb
}
}
/* 带返回值的关中断函数,可以嵌套,可以在中断里面使用 */ (2)
#define portSET_INTERRUPT_MASK_FROM_ISR() ulPortRaiseBASEPRI()
ulPortRaiseBASEPRI( void )
{
uint32_t ulReturn, ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY; (2)-①
__asm
{
mrs ulReturn, basepri (2)-②
msr basepri, ulNewBASEPRI (2)-③
dsb
isb
}
return ulReturn; (2)-④
}
1.1 不带返回值的关中断函数:
(1):不带返回值的关中断函数,不能嵌套,不能在中断中使用。不带返回值的意思是:在往BASEPRI写入新值时,不用先将BASEPRI的值保存起来,即不用管当前的中断状态是怎么样的。
(1)-①:configMAX_SYSCALL_INTERRUPT_PRIORITY是 一 个 在FreeRTOSConfig.h 中定义的宏,即要写入BASEPRI寄存器中的值。该宏默认定义为191,高四位有效,即0xb0(11),即优先级大鱼等于11的终端都会被屏蔽,11以内的中断则不受FreeRTOS管理。
(1)-②:将configMAX_SYSCALL_INTERRUPT_PRIORITY的值写入BASEPRI寄存器,实现关中断(准确来说是关部分中断)。
1.2 带返回值的关中断函数:
(2):带返回值的关中断函数,可以嵌套,可以在中断里使用。带返回值的意思是:在往BASEPRI写入新值时,先将BASEPRI的值保存起来,在更新完BASEPRI的值的时候,将之前保存的BASEPRI的值返回,返回的值作为形参传入开中断函数中。
(2)-①:同(1)-①。
(2)-②:保存BASEPRI的值到ulReturn,记录当前哪些中断关闭。
(2)-③: 更新 BASEPRI 的值。
(2)-④:返回原来BASEPRI 的值。
2、开中断
FreeRTOS 开中断的函数在 portmacro.h 中定义。
/* 不带中断保护的开中断函数 */
#define portENABLE_INTERRUPTS() vPortSetBASEPRI( 0 ) (2)
/* 带中断保护的开中断函数 */
#define portCLEAR_INTERRUPT_MASK_FROM_ISR(x) vPortSetBASEPRI(x) (3)
void vPortSetBASEPRI( uint32_t ulBASEPRI ) (1)
{
__asm
{
msr basepri, ulBASEPRI
}
}
(1):开中断函数,具体是将传进来的形参更新到BASEPRI寄存器。根据传进来形参的不同,分为中断保护版本和非中断保护版本。
(2):不带中分段保护的开中断函数,直接将BASEPRI的值设为0,与portDISABLE_INTERRUPTS()成对使用。
(3):带中断保护的中断函数,将上一次关中断时保存的BASEPRI的值作为形参,与 portSET_INTERRUPT_MASK_FROM_ISR()成对使用。
四、进入/退出临界中断
进入和退出临界中断的宏分为中断保护版本和非中断保护版本,但最终都是通过开/关中断来实现的。
1、进入临界段
1.1 不带中断保护版本,不能嵌套
/* ==========进入临界段, 不带中断保护版本,不能嵌套=============== */
/* 在 task.h 中定义 */
#define taskENTER_CRITICAL() portENTER_CRITICAL()
/* 在 portmacro.h 中定义 */
#define portENTER_CRITICAL() vPortEnterCritical()
/* 在 port.c 中定义 */
void vPortEnterCritical( void )
{
portDISABLE_INTERRUPTS();
uxCriticalNesting++; (1)
if ( uxCriticalNesting == 1 ) (2)
{
configASSERT( ( portNVIC_INT_CTRL_REG & portVECTACTIVE_MASK ) == 0 );
}
}
/* 在 portmacro.h 中定义 */
#define portDISABLE_INTERRUPTS() vPortRaiseBASEPRI()
/* 在 portmacro.h 中定义 */
static portFORCE_INLINE void vPortRaiseBASEPRI( void )
{
uint32_t ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY;
__asm
{
msr basepri, ulNewBASEPRI
dsb
isb
}
}
(1):uxCriticalNesting是在port.c中定义的静态变量,表示临界段嵌套计数器,默认初始值为0xaaaaaaaa,在调度器启动时会被重新初始化为0。
(2)如果uxCriticalNesting等于1,即一层嵌套,要确保当前没有中断活跃,即内核外设 SCB 中的中断和控制寄存器 SCB_ICSR 的低 8 位要等于 0。
1.2 带中断保护版本,能嵌套
/* ==========进入临界段,带中断保护版本,可以嵌套=============== */
/* 在 task.h 中定义 */
#define taskENTER_CRITICAL_FROM_ISR() portSET_INTERRUPT_MASK_FROM_ISR()
/* 在 portmacro.h 中定义 */
#define portSET_INTERRUPT_MASK_FROM_ISR() ulPortRaiseBASEPRI()
/* 在 portmacro.h 中定义 */
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;
}
2、退出临界段
2.1 不带中断保护版本,不能嵌套
/* ==========退出临界段,不带中断保护版本,不能嵌套=============== */
/* 在 task.h 中定义 */
#define taskEXIT_CRITICAL() portEXIT_CRITICAL()
/* 在 portmacro.h 中定义 */
#define portEXIT_CRITICAL() vPortExitCritical()
/* 在 port.c 中定义 */
void vPortExitCritical( void )
{
configASSERT( uxCriticalNesting );
uxCriticalNesting--;
if ( uxCriticalNesting == 0 )
{
portENABLE_INTERRUPTS();
}
}
/* 在 portmacro.h 中定义 */
#define portENABLE_INTERRUPTS() vPortSetBASEPRI( 0 )
/* 在 portmacro.h 中定义 */
static portFORCE_INLINE void vPortSetBASEPRI( uint32_t ulBASEPRI )
{
__asm
{
msr basepri, ulBASEPRI
}
}
2.2 带中断保护版本,能嵌套
/* ==========退出临界段,带中断保护版本,可以嵌套=============== */
/* 在 task.h 中定义 */
#define taskEXIT_CRITICAL_FROM_ISR( x ) portCLEAR_INTERRUPT_MASK_FROM_ISR( x )
/* 在 portmacro.h 中定义 */
#define portCLEAR_INTERRUPT_MASK_FROM_ISR(x) vPortSetBASEPRI(x)
/* 在 portmacro.h 中定义 */
static portFORCE_INLINE void vPortSetBASEPRI( uint32_t ulBASEPRI )
{
__asm
{
msr basepri, ulBASEPRI
}
}
五、临界段代码应用
在 FreeRTOS 中,对临界段的保护出现在两种场合,一种是在中断场合一种是在非中断场合。
/* 在中断场合,临界段可以嵌套 */
{
uint32_t ulReturn;
/* 进入临界段,临界段可以嵌套 */
ulReturn = taskENTER_CRITICAL_FROM_ISR();
/* 临界段代码 */
/* 退出临界段 */
taskEXIT_CRITICAL_FROM_ISR( ulReturn );
}
/* 在非中断场合,临界段不能嵌套 */
{
/* 进入临界段 */
taskENTER_CRITICAL();
/* 临界段代码 */
/* 退出临界段*/
taskEXIT_CRITICAL();
}
参考:[野火®]《FreeRTOS 内核实现与应用开发实战—基于STM32》
来源:CSDN
作者:Eleven-boy
链接:https://blog.csdn.net/qq_38422317/article/details/104276274