一、什麼是臨界段
臨界段就是一段在執行的時候不能被打斷的代碼。在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》