临界段
临界段就是一段在执行的时候不能被中断的代码段。在FreeRTOS最常用的情况是对全局变量的操作。
基本上只有和系统调度,外部中断能打断临界段。在FreeRTOS中,系统调度也就是 PendSV 中断,所以对临界段的保护还是在于对中断的开和关的控制。中断被关闭之后,临界段就无法响应中断,从而做到保护临界段。
Cortex-M 内核专门设置了一条 CPS 指令来实现快速开关中断,PRIMASK 和 FAULTMAST 是 Cortex-M 内核 里面三个中断屏蔽寄存器中的两个,还有一个是 BASEPRI。用法如下。
CPSID I ; PRIMASK=1; 关中断
CPSIE I ; PRIMASK=0; 开中断
CPSID F ; FAULTMASK=1; 关异常
CPSIE F ; FAULTMASK=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 //值为11,大于11的一律不能响应
dsb
isb
}
}
因为不带返回值,即返回值为空,因此不能嵌套。
configMAX_SYSCALL_INTERRUPT_PRIORITY 是一个在FreeRTOSConfig.h 中定义的宏,即要写入到 BASEPRI 寄存器的值。该宏默认定义为 191,高四位有效,即等于 0xb0或11。表示优先级大于等于 11 的中断都会被屏蔽,11 以内的中断则不受 FreeRTOS 管理。
/* 带返回值的关中断函数 */
#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 //把原先的basepri值保存在返回值中
msr basepri, ulNewBASEPRI //设置basepri的新值
dsb
isb
}
return ulReturn;
}
这个函数返回了一个32位无符号的整型变量,这意味着这个函数可以有返回值,也可以进行嵌套。
开中断
#define portENABLE_INTERRUPTS() vPortSetBASEPRI( 0 )
#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
}
}
开中断函数,具体是将传进来的形参更新到 BASEPRI 寄存器。根据传进来形参的不同,分为中断保护版本与非中断保护版本。
不带中断保护的开中断函数, 直接将 BASEPRI 的值设置为 0,与portDISABLE_INTERRUPTS()成对使用。
带中断保护的开中断函数, 将上一次关中断时保存的 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 中定义 */
#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
}
}
uxCriticalNesting 是在 port.c 中定义的静态变量,表示临界段嵌套计数 器 , 默 认 初 始 化 为 0xaaaaaaaa , 在 调 度 器 启 动 时 会 被 重 新 初 始 化 为 0 :
vTaskStartScheduler()->xPortStartScheduler()->uxCriticalNesting = 0
如果 uxCriticalNesting 等于 1,即一层嵌套,要确保当前没有中断活跃,即内核外设 SCB 中的中断和控制寄存器 SCB_ICSR 的低 8 位要等于 0。
/* ==========进入临界段,带中断保护版本,可以嵌套=============== */
/* 在 task.h 中定义 */
#define taskENTER_CRITICAL_FROM_ISR() portSET_INTERRUPT_MASK_FROM_ISR()
/* 在 portmacro.h 中定义 */
#define portSET_INTERRUPT_MASK_FROM_ISR()
ulPortRaiseBASEPRI() 7
/* 在 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;
}
退出临界段
/* ==========退出临界段,不带中断保护版本,不能嵌套=============== */
/* 在 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
}
}
/* ==========退出临界段,带中断保护版本,可以嵌套=============== */
/* 在 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();
}
PS:最近这几篇感觉好水,这部分能理解就行,和源码移植没多大关系,重点在于理解RTOS的机制。 因此打算最近开始直接移植,基础这里看情况更新。