1. 什么是临界段
临界段用一句话概括就是一段在执行的时候不能被中断的代码段。
那么什么情况下临界段会被打断?
一个是系统调度,还有一个就是外部中断。
因此FreeRTOS对临界段的保护最终还是回到对中断的开和关的控制。
2. Cortex-M内核快速关中断指令
CPSID I ;PRIMASK=1 ;关中断
CPSIE I ;PRIMASK=0 ;开中断
CPSID F ;FAULTMASK=1 ;关异常
CPSIE F ;FAULTMASK=0 ;开异常
名字 | 功能描述 |
---|---|
PRIMASK | 这是个只有单一比特的寄存器。 在它被置 1 后,就关掉所有可屏蔽的异常,只剩下 NMI 和硬 FAULT可以响应。它的缺省值是 0,表示没有关中断。 |
FAULTMASK | 这是个只有 1 个位的寄存器。当它置 1 时,只有 NMI 才能响应,所有其它的异常,甚至是硬 FAULT,也通通闭嘴。它的缺省值也是 0,表示没有关异常。 |
BASEPRI | 这个寄存器最多有 9 位(由表达优先级的位数决定)。它定义了被屏蔽优先级的阈值。当它被设成某个值后,所有优先级号大于等于此值的中断都被关(优先级号越大,优先级越低)。但若被设成 0,则不关闭任何中断, 0 也是缺省值。 |
3. 关中断
在FreeRTOS中,对中断的开和关是通过操作BASEPRI寄存器来实现的,即大于等于BASEPRI的值的中断会被屏蔽,小于BASEPRI的值的中断则不会被屏蔽,不受FreeRTOS管理。用户可以设置BASEPRI的值来选择性的给一些非常紧急的中断留一条后路。
在portmacro.h文件中有两个宏定义:
/* 不带返回值的关中断函数,不能嵌套,不能在中断里面使用 */
#define portDISABLE_INTERRUPTS() vPortRaiseBASEPRI()
/* 带返回值的关中断函数,可以嵌套,可以在中断里面使用 */
#define portSET_INTERRUPT_MASK_FROM_ISR() ulPortRaiseBASEPRI()
4. 开中断
两个宏定义:
/* 不带中断保护的开中断函数 */
#define portENABLE_INTERRUPTS() vPortSetBASEPRI( 0 )
/* 带中断保护的开中断函数 */
#define portCLEAR_INTERRUPT_MASK_FROM_ISR(x) vPortSetBASEPRI(x)
5. 进入临界段
进入临界段,不带中断保护版本,不能嵌套
/* 在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()
6. 退出临界段
退出临界段,不带中断保护版本,不能嵌套
/* 在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)
7. 临界段的应用
/* 在中断场合,临界段可以嵌套 */
{
uint32_t ulreturn;
/* 进入临界段,临界段可以嵌套 */
ulReturn = taskENTER_CRITICAL_FROM_ISR();
/* 临界段代码 */
/* 退出临界段 */
taskEXIT_CRITICAL();
}