目录
- 临界段的定义
- Cortex-M内核快速关开关中断的指令
- 关中断
- 开中断
- 进入临界段的宏
- 退出临界段的宏
- 进入临界段,不带中断保护, 不能嵌套
- 进入临界段,带中断保护版本,可以嵌套
- 退出临界段,不带中断保护版本,不能嵌套
- 退出临界段,带中断保护版本,可以嵌套
- 临界段代码的应用
临界段的定义
临界段是指在执行时不能被中断的代码段。在FreeRTOS中,临界段最经常出现的地方就是对全局变量的操作。
Q1:什么情况下临界段会被打断?
系统调度和外部中断。
在FreeRTOS中,系统调度最终也是产生PendSV中断,在PendSVHandler中实现任务的切换,所以还是归结为中断。
FreeRTOS对临界段的保护就是对中断的开和关的控制。
Cortex-M内核快速关开关中断的指令
CPSID I ;PRIMASK=1 ;关中断
CPSIE I ;PRIMASK=0 ;开中断
CPSID F ;PRIMASK=1 ;关异常
CPSIE F ;PRIMASK=1 ;开异常
PRIMASK 、 FAULTMAST 和 BASEPRI 是Cotex-M内核中3个中断屏蔽寄存器。
关中断
FreeRTOS关中断函数在portmacro.h中定义,分为带返回值和不带返回值两种。
/*不带返回值的关中断函数不能嵌套,不能在中断中使用 */
#define portDISABLE_INTERRUPTS() vPortRaiseBASEPRI()
static portFORCE_INLINE void vPortRaiseBASEPRI( void )
{
uint32_t ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY;
__asm
{
/* Set BASEPRI to the max syscall priority to effect a critical
section. */
msr basepri, ulNewBASEPRI
dsb
isb
}
}
不带返回值的意思是,在向BASEPRI写入新值时,不用先将BASEPRI的值保存起来,即不用考虑当前中断状态是怎么样的,这意味着这样的函数不能在中断中调用。
#define configMAX_SYSCALL_INTERRUPT_PRIORITY 191 /* 高四位有效,即等于0xb0,或者是11 */
它是在FreeConfig.h中定义的宏,即大于等于11的中断都会被屏蔽。
ps:这个值可以自己设置,实际开发中,一般设置为5.
在FreeRTOS中,凡是看到FROM_ISR结尾的函数/宏定义都是在中断中使用的。
#define portSET_INTERRUPT_MASK_FROM_ISR() ulPortRaiseBASEPRI()
static portFORCE_INLINE uint32_t ulPortRaiseBASEPRI( void )
{
uint32_t ulReturn, ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY;
__asm
{
/* Set BASEPRI to the max syscall priority to effect a critical
section. */
mrs ulReturn, basepri
msr basepri, ulNewBASEPRI
dsb
isb
}
return ulReturn;
}
带返回值的关中断函数可以嵌套,可以在中断中使用。带返回值的意思是,在BASEPRI写入新值时,先将BASEPRI的值先保存起来,在更新完成BASEPRI的值时,将之前保存好的BASEPRI的值返回,作为形参传入开中断函数中。
开中断
FreeRTOS开中断函数在portmacro.h中定义
/*不带中断保护的开中断函数*/
#define portSET_INTERRUPT_MASK_FROM_ISR() ulPortRaiseBASEPRI()
/*带中断保护的开中断函数*/
#define portCLEAR_INTERRUPT_MASK_FROM_ISR(x) vPortSetBASEPRI(x)
static portFORCE_INLINE void vPortSetBASEPRI( uint32_t ulBASEPRI )
{
__asm
{
/* Barrier instructions are not used as this function is only used to
lower the BASEPRI value. */
msr basepri, ulBASEPRI
}
}
根据ulBASEPRI 的参数,选择保护和不带中断中断保护的版本
不带中断保护的开中断函数,直接将BASEPRI设置为0,与portDISABLE_INTERRUPTS()成对使用。
带中断保护的开中断函数,将上一次关中断保存的BASEPRI的值作为形参,与portSET_INTERRUPT_MASK_FROM_ISR()成对使用
进入临界段的宏
/*进入临界段,不带中断保护版本,不能嵌套*/
/* 在task.h中定义 */
#define taskENTER_CRITICAL() portENTER_CRITICAL()
/* 在portmacro.h中定义 */
#define portENTER_CRITICAL() vPortEnterCritical()
/*进入临界段,带中断保护版本,可以嵌套*/
/* 在portmacro.h中定义 */
#define taskENTER_CRITICAL_FROM_ISR() portSET_INTERRUPT_MASK_FROM_ISR()
/* 在portmacro.h中定义 */
#define portSET_INTERRUPT_MASK_FROM_ISR() ulPortRaiseBASEPRI()
退出临界段的宏
/*退出临界段,不带中断保护版本,不能嵌套*/
/* 在task.h中定义 */
#define taskEXIT_CRITICAL() portEXIT_CRITICAL()
/* 在portmacro.h中定义 */
#define portEXIT_CRITICAL() vPortExitCritical()
/*退出临界段,带中断保护版本,可以嵌套*/
/* 在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)
进入临界段,不带中断保护, 不能嵌套
/*task.h中定义*/
#define taskENTER_CRITICAL() portEXIT_CRITICAL()
/*在portmacro.h中定义*/
#define portENTER_CRITICAL() vPortEnterCritical()
/*在port.c中定义*/
void vPortEnterCritical( void )
{
portDISABLE_INTERRUPTS();
uxCriticalNesting++;
/* This is not the interrupt safe version of the enter critical function so
assert() if it is being called from an interrupt context. Only API
functions that end in "FromISR" can be used in an interrupt. Only assert if
the critical nesting count is 1 to protect against recursive calls if the
assert function also uses a critical section. */
if( uxCriticalNesting == 1 )
{
configASSERT( ( portNVIC_INT_CTRL_REG & portVECTACTIVE_MASK ) == 0 );
}
}
/*在portmacro.h中定义*/
#define portDISABLE_INTERRUPTS() vPortRaiseBASEPRI()
static portFORCE_INLINE void vPortRaiseBASEPRI( void )
{
uint32_t ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY;
__asm
{
/* Set BASEPRI to the max syscall priority to effect a critical
section. */
msr basepri, ulNewBASEPRI
dsb
isb
}
}
ps: uxCriticalNesting是prot.c中定义的静态变量,表示临界段嵌套计数器,默认初始化为0xaaaaaaaa,在调度器启动时会被重新初始化为0:vTaskScheduler() -> xPortStartScheduler() -> uxCriticalNesting = 0
如果uxCriticalNesting=1,即一层嵌套,要确保当前没有中断活跃,即内核外设SCB中的中断和控制寄存器SCB_ICSR的低8位要等于0.
进入临界段,带中断保护版本,可以嵌套
/*在task.c中定义*/
#define taskENTER_CRITICAL_FROM_ISR() portSET_INTERRUPT_MASK_FROM_ISR()
/*在portmacro.h中定义*/
#define portSET_INTERRUPT_MASK_ISR() u1PortRaiseBASEPRI()
static portFORCE_INLINE uint32_t ulPortRaiseBASEPRI( void )
{
uint32_t ulReturn, ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY;
__asm
{
/* Set BASEPRI to the max syscall priority to effect a critical
section. */
mrs ulReturn, basepri
msr basepri, ulNewBASEPRI
dsb
isb
}
return ulReturn;
}
退出临界段,不带中断保护版本,不能嵌套
/*在task.c中定义*/
#define taskEXIT_CRITCAL() portEXIT_CRITCAL()
/*在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 )
static portFORCE_INLINE void vPortSetBASEPRI( uint32_t ulBASEPRI )
{
__asm
{
/* Barrier instructions are not used as this function is only used to
lower the BASEPRI value. */
msr basepri, ulBASEPRI
}
}
退出临界段,带中断保护版本,可以嵌套
/*在task.c中定义*/
#define taskEXIT_CRITICAL_FROM_ISR(x) portCLEAR_INTERRUPT_MASK_FROM_IS(x)
/*在portmacro.h中定义*/
#define portCLEAR_INTERRUPT_MASK_FROM_ISR(x) vPortSetBASEPRI(x)
static portFORCE_INLINE void vPortSetBASEPRI( uint32_t ulBASEPRI )
{
__asm
{
/* Barrier instructions are not used as this function is only used to
lower the BASEPRI value. */
msr basepri, ulBASEPRI
}
}
临界段代码的应用
/*在中断场合,临界段可以嵌套*/
{
unit32_t ulReturn;
/*进入临界段,临界段可以嵌套*/
ulReturn =taskENTER_CRITCAL_FROM_ISR();
/*临界段代码*/
/*退出临界段*/
taskEXIT_CRITICAL_FROM_ISR(ulReturn );
}
/*在非中断场合,临界段不能嵌套*/
{
/*进入临界段,临界段可以嵌套*/
taskENTER_CRITICAL();
/*临界段代码*/
/*退出临界段*/
taskEXIT_CRITICAL();
}
学习于《FreeRTOS内核实现与应用开发实战指南–基于stm32》
b站视频地址:https://www.bilibili.com/video/BV1Jx411X7NS/