一、介绍
临界段最常出现在对一些全局变量进行操作的场景。
1.1 临界段的定义
临界段是指在多任务系统中,一段需要独占访问共享资源的代码。在这段代码执行期间,必须确保没有任何其他任务或中断可以访问或修改相同的共享资源。
临界段的主要目的是防止多个任务或中断同时访问共享资源,从而避免数据不一致或竞态条件。
1.2 临界段的特点
-
1、互斥访问:
-
临界段内的代码必须确保在任何时刻只有一个任务或中断可以访问共享资源。
-
其他任务或中断必须等待,直到当前任务或中断完成对共享资源的访问。
-
-
2、短小精悍:
-
临界段的代码应该尽可能短小,以减少对系统性能的影响。
-
长时间的临界段可能会导致系统响应延迟,影响实时性。
-
-
3、明确的入口和出口:
-
临界段必须有明确的入口和出口。
-
入口处通常会禁用中断,出口处会恢复中断。
-
1.3 临界段的实现方式
在RTOS中,临界段可以通过以下几种方式实现:
-
1、禁用中断:
-
直接禁用所有中断:通过设置硬件寄存器(如Cortex-M的 PRIMASK)来禁用所有中断。
-
设置中断优先级阈值:通过设置硬件寄存器(如Cortex-M的 BASEPRI)来屏蔽优先级高于某个值的中断。
-
-
2、使用互斥量(Mutex):
-
互斥量是一种同步原语,用于确保对共享资源的互斥访问。
-
任务在访问共享资源前必须先获取互斥量,访问完成后释放互斥量。
-
// 创建互斥量
SemaphoreHandle_t xMutex = xSemaphoreCreateMutex();// 获取互斥量
if (xSemaphoreTake(xMutex, portMAX_DELAY) == pdTRUE)
{// 临界段代码
}// 释放互斥量
xSemaphoreGive(xMutex);
-
3、使用信号量(Semaphore):
-
信号量是一种计数器,用于控制对共享资源的访问。
-
任务在访问共享资源前必须先获取信号量,访问完成后释放信号量。
-
// 创建信号量
SemaphoreHandle_t xSemaphore = xSemaphoreCreateBinary();// 获取信号量
if (xSemaphoreTake(xSemaphore, portMAX_DELAY) == pdTRUE)
{// 临界段代码
}// 释放信号量
xSemaphoreGive(xSemaphore);
-
4、使用自旋锁(Spinlock):
-
自旋锁是一种简单的同步机制,任务在获取锁时会不断尝试,直到获取成功。
-
自旋锁通常用于短时间的临界段,以减少上下文切换的开销。
-
二、Cortex-M内核快速关中断指令
为了快速地开关中断, Cortex-M 内核专门设置了一条 CPS 指令,有 4 种用法,具体如下:
CPSID I ;PRIMASK=1 ; //关中断
CPSIE I ;PRIMASK=0 ; //开中断
CPSID F ;FAULTMASK=1 ; //关异常
CPSIE F ;FAULTMASK=0 ; //开异常
在ARM Cortex-M系列处理器中,PRIMASK、FAULTMASK 和 BASEPRI 是三个用于控制中断和异常处理的系统级寄存器。
2.1 PRIMASK
-
功能:禁用除NMI(不可屏蔽中断)和Hard Fault(硬件故障)之外的所有异常和中断。
-
作用机制:设置 PRIMASK (通过 MSR PRIMASK, #1 或 CPSID I;)把当前中断优先级提为0,来屏蔽除NMI和Hard Fault之外的所有异常和中断。
-
典型用途:用于快速进入临界区,保护关键代码段不被中断打断,例如在RTOS任务切换或共享资源访问等应用中。
-
特点:简单易用,但对系统实时性影响较大,长时间开启可能导致高优先级中断无法响应。
是一个单一比特的寄存器。缺省值是0,表示没有关中断。
2.2 FAULTMASK
-
功能:禁用除NMI之外的所有异常和中断,包括Hard Fault。
-
作用机制:设置 FAULTMASK(通过MSR FAULTMASK, #1或CPSID F ;)会把当前中断优先级提升到-1,仅允许NMI。
-
典型用途:在异常处理程序中临时屏蔽可能引发嵌套故障的操作(如内存访问)。
-
特点:比PRIMASK更严格,可能影响系统稳定性,仅在特权模式(Privileged Mode)下可修改。
是一个只有1位的寄存器。缺省值是0,表示没有关异常。
2.3 BASEPRI
-
功能:基于优先级的动态中断屏蔽,仅屏蔽优先级低于阈值的中断。
-
作用机制:设置 BASEPRI (通过 MSR BASEPRI, #priority)允许优先级低于阈值的中断继续执行,高于阈值的中断被屏蔽。
-
典型用途:灵活控制中断优先级,允许高优先级任务/中断优先执行,同时屏蔽低优先级中断。
-
特点:更精细的控制,避免完全禁用所有中断,但需要合理设置优先级阈值,否则可能导致意外屏蔽。
三、关中断
关中断函数分为带返回值和不带返回值两种。
3.1 不带返回值的关中断函数
static portFORCE_INLINE void vPortRaiseBASEPRI( void ) // 不带返回值的函数是不能嵌套的
{
uint32_t ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY;__asm{/* Set BASEPRI to the max syscall priority to effect a criticalsection. */msr basepri, ulNewBASEPRI // 11 大于11的中断不能被响应 小于11则可以 根据 configMAX_SYSCALL_INTERRUPT_PRIORITY 的值来配置dsbisb}
}
- dsb(Data Synchronization Barrier):数据同步屏障,确保所有之前的内存访问操作(如读写操作)都完成后再继续执行后续代码。
- isb(Instruction Synchronization Barrier):指令同步屏障,确保所有之前的指令都执行完成后再继续执行后续代码。
3.2 带返回值的关中断函数
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 criticalsection. */mrs ulReturn, basepri // 先将 basepri 的值保存在 返回值中msr basepri, ulNewBASEPRI // 再设置 basepri 的值dsbisb}return ulReturn;
}
四、开中断
static portFORCE_INLINE void vPortSetBASEPRI( uint32_t ulBASEPRI )
{__asm{/* Barrier instructions are not used as this function is only used tolower the BASEPRI value. */msr basepri, ulBASEPRI}
}
五、进入/退出临界段的宏
5.1 进入临界段的宏
5.1.1 不带中断保护
#define taskENTER_CRITICAL() portENTER_CRITICAL() // task.h中定义#define portENTER_CRITICAL() vPortEnterCritical() // portmacro.h中定义#define portDISABLE_INTERRUPTS() vPortRaiseBASEPRI() // portmacro.h中定义
5.1.2 带中断保护
#define taskENTER_CRITICAL_FROM_ISR() portSET_INTERRUPT_MASK_FROM_ISR() // task.h 中定义#define portSET_INTERRUPT_MASK_FROM_ISR() ulPortRaiseBASEPRI() // portmacro.h 中定义
5.2 退出临界段的宏
5.2.1 不带中断保护
#define taskEXIT_CRITICAL() portEXIT_CRITICAL() // task.h 中定义#define portEXIT_CRITICAL() vPortExitCritical() // portmacro.h 中定义#define portENABLE_INTERRUPTS() vPortSetBASEPRI( 0 ) //portmacro.h 中定义
5.2.2 带中断保护
#define taskEXIT_CRITICAL_FROM_ISR( x ) portCLEAR_INTERRUPT_MASK_FROM_ISR( x ) // task.h 中定义#define portCLEAR_INTERRUPT_MASK_FROM_ISR(x) vPortSetBASEPRI(x) // portmacro.h 中定义
六、临界段代码的应用
在 FreeRTOS 中,对临界段的保护出现在两种场合,一种是在中断场合,一种是在非中断场合。
6.1 中断场合
// 在中断场合,临界段可以嵌套
{uint32_t ulReturn;// 进入临界段,临界段可以嵌套ulReturn = taskENTER_CRITICAL_FROM_ISR();// 临界段代码// 退出临界段taskEXIT_CRITICAL_FROM_ISR( ulReturn );
}
6.2 非中断场合
// 非中断场合,临界段不能嵌套
{// 进入临界段taskENTER_CRITICAL();// 临界段代码// 退出临界段taskEXIT_CRITICAL();
}