一、介紹
臨界段最常出現在對一些全局變量進行操作的場景。
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();
}