FreeRTOS——临界段保护

一、什么是临界段

临界段就是一段在执行的时候不能被打断的代码。在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》

你可能感兴趣的:(freeRTOS)