本文主要是对于 FreeRTOS 中临界段的保护的详细解释,代码大部分参考了野火 FreeRTOS 教程配套源码,作了一小部分修改。
临界段就是一段在执行的时候不能被中断的代码段。
**临界段(Critical Section)**是指在多任务或多线程环境下,一段代码或一组代码,在执行期间对共享资源进行访问或操作的临界区域。临界段中的代码只能被一个任务或线程单独执行,以确保对共享资源的访问是正确和一致的。
在进入临界段之前,任务或线程需要获得对应的同步机制(如互斥锁、信号量等)的控制权。一旦获得控制权,任务或线程可以安全地访问临界段中的共享资源。在临界段执行完成后,任务或线程释放同步机制的控制权,让其他任务或线程可以进入临界段执行。
CPSID I ;PRIMASK=1 ;关中断
CPSIE I ;PRIMASK=0 ;开中断
CPSID F ;FAULTMASK=1 ;关异常
CPSIE F ;FAULTMASK=0 ;开异常
野火的教程在这方面语焉不详,笔者自行理解了一下:
所谓可嵌套的中断操作函数,指的是该中断函数有返回值,返回该函数操作前的中断屏蔽等级,便于其他函数使用该返回值恢复现场。
对于可嵌套的中断操作函数在保护临界区方面有如下作用:
进入临界区时,关闭中断,此时可嵌套的关闭中断函数可以保存关闭前的屏蔽等级,在退出临界区可以使用该保存的屏蔽等级进行屏蔽等级的恢复设置。
代码例程如下:
// 全局共享资源
volatile int counter = 0;
// 中断处理程序
void interrupt_handler()
{
// 进入临界区(关闭中断)
Interruption_Mask_Level = disable_interrupt();
// 访问共享资源
counter++;
// 退出临界区(打开中断)
enable_interrupt(Interruption_Mask_Level);
}
// 主程序
int main()
{
// 启用中断
while (1)
{
// 执行其他任务
}
return 0;
}
主要分为三类函数:
其中开关中断函数被进出临界段函数调用。
//关中断函数
//不带返回值的关中断函数,不能嵌套,不能在中断里面使用
#define portDISABLE_INTERRUPTS() vPortRaiseBASEPRI()
//带返回值的关中断函数,可以嵌套,可以在中断里面使用
#define portSET_INTERRUPT_MASK_FROM_ISR() ulPortRaiseBASEPRI()
//中断屏蔽等级的设置
#define configMAX_SYSCALL_INTERRUPT_PRIORITY 191 /* 高四位有效,即等于0xb0,或者是11 */
//不带返回值,不能嵌套
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 //宏定义中 basepri = 11,也就是优先级大于11的中断不被响应
dsb
isb
}
}
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;
}
//开中断函数
// 不可嵌套
#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
}
}
进出临界段函数抽象了好几层,如下:
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 )
portmacro.h
//进出临界段
#define portENTER_CRITICAL() vPortEnterCritical()
#define portEXIT_CRITICAL() vPortExitCritical()
展示不带中断保护版本的进出临界段函数代码:
vPortEnterCritical(): 这个函数的作用是进入临界区。它首先调用portDISABLE_INTERRUPTS()函数来禁用中断,阻止其他中断干扰。然后,它将uxCriticalNesting计数器加一,表示进入了一个新的临界区。如果uxCriticalNesting计数器的值为1,即当前进入的是第一个临界区,那么可以使用configASSERT()来进行断言检查,确保该函数不是在中断上下文中调用。
vPortExitCritical(): 这个函数的作用是退出临界区。它首先检查uxCriticalNesting计数器的值,确保当前确实在一个临界区中。然后,它将uxCriticalNesting计数器减一,表示退出当前的临界区。如果uxCriticalNesting计数器的值减为0,即没有进入任何临界区了,那么可以调用portENABLE_INTERRUPTS()函数来重新启用中断,允许其他中断恢复执行。
//进入临界段
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 );
}
}
//退出临界段
void vPortExitCritical( void )
{
//configASSERT( uxCriticalNesting );
uxCriticalNesting--;
if( uxCriticalNesting == 0 )
{
portENABLE_INTERRUPTS();
}
}
//进出临界段
//带返回值的关中断函数,可以嵌套,可以在中断里面使用
#define portSET_INTERRUPT_MASK_FROM_ISR() ulPortRaiseBASEPRI()
//可嵌套开中断函数
#define portCLEAR_INTERRUPT_MASK_FROM_ISR(x) vPortSetBASEPRI(x)
如果您觉得本文写得不错,可以点个赞激励一下作者!
如果您发现本文的问题,欢迎在评论区或者私信共同探讨!
共勉!