FreeRTOS——臨界段保護

一、什麼是臨界段

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章