FreeRTOS内核详解(1) —— 临界段保护原理
什么是臨界段
臨界段用一句話概括就是一段在執(zhí)行的時候不能被中斷的代碼段。 在 FreeRTOS 里面,這個臨界段最常出現(xiàn)的就是對全局變量的操作,由于不同任務(wù)間可以切換運行,當(dāng)一個任務(wù)在訪問某個全局變量時,這時其他任務(wù)切入,改變了該全局變量,再回到上個任務(wù)時訪問該變量已經(jīng)不是當(dāng)時的值,這種情況可能會導(dǎo)致不可意料的后果。
FreeRTOS 源碼中就有多處臨界段的處理,跟 FreeRTOS 一樣,uCOS-II 和 uCOS-III 源碼中都是有臨界段的,而 RTX 的源碼中不存在臨界段。另外,除了 FreeRTOS 操作系統(tǒng)源碼所帶的臨界段以外,用戶寫應(yīng)用的時候也有臨界段的問題,比如以下兩種:
讀取或者修改變量(尤其是任務(wù)間通信的全局變量)的代碼。
調(diào)用公共函數(shù)的代碼,特別是不可重入函數(shù),如果多個任務(wù)都訪問這個函數(shù),結(jié)果是可想而知的。
注意:由于臨界段中需要關(guān)閉中斷,因此會影響某些緊急中斷的處理速度,同時由于臨界段中關(guān)閉了任務(wù)調(diào)度,也會影響系統(tǒng)實時性,總之臨界段的執(zhí)行時間要求越短越好。
Cortex-M 內(nèi)核快速關(guān)中斷指令
為了快速地開關(guān)中斷, Cortex-M 內(nèi)核專門設(shè)置了一條 CPS 指令,有 4 種用法
CPSID I ;PRIMASK=1 ;關(guān)中斷 CPSIE I ;PRIMASK=0 ;開中斷 CPSID F ;FAULTMASK=1 ;關(guān)異常 CPSIE F ;FAULTMASK=0 ;開異常PRIMASK 和 FAULTMAST 是 Cortex-M 內(nèi)核 里面三個中斷屏蔽寄存器中的兩個,還有一個是 BASEPRI,有關(guān)這三個寄存器的詳細用法
在 FreeRTOS 中,對中斷的開和關(guān)是通過操作 BASEPRI 寄存器來實現(xiàn)的,即大于等于 BASEPRI 的值的中斷會被屏蔽,小于 BASEPRI 的值的中斷則不會被屏蔽,不受FreeRTOS 管理。用戶可以設(shè)置 BASEPRI 的值來選擇性的給一些非常緊急的中斷留一條后路。
進出臨界段源碼分析
進入和退出臨界段的宏在 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 )進入和退出臨界段的宏分中斷保護版本和非中斷版本, 但最終都是通過開/關(guān)中斷來實現(xiàn)。有關(guān)開/光中斷的底層代碼我們已經(jīng)講解,那么接下來的退出和進入臨界段的代碼配套注釋來理解即可。
進入臨界段
進入臨界段,不帶中斷保護版本且不能嵌套的代碼實現(xiàn)
1. 不帶中斷保護版本
/* ==========進入臨界段, 不帶中斷保護版本,不能嵌套=============== */ /* 在 task.h 中定義 */ #define taskENTER_CRITICAL() portENTER_CRITICAL()/* 在 portmacro.h 中定義 */ #define portENTER_CRITICAL() vPortEnterCritical()/* 在 port.c 中定義 */ void vPortEnterCritical( void ) {portDISABLE_INTERRUPTS();uxCriticalNesting++; if ( uxCriticalNesting == 1 ) {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, ulNewBASEPRIdsbisb} }//FreeRTOS_config.h中 #define configLIBRARY_LOWEST_INTERRUPT_PRIORITY 15 //中斷最低優(yōu)先級 #define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 10 //系統(tǒng)可管理的最高中斷優(yōu)先級 #define configKERNEL_INTERRUPT_PRIORITY ( configLIBRARY_LOWEST_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) ) #define configMAX_SYSCALL_INTERRUPT_PRIORITY ( configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )uxCriticalNesting 是在 port.c 中定義的靜態(tài)變量,表示臨界段嵌套計數(shù)器 , 默 認 初 始 化 為 0xaaaaaaaa , 在 調(diào) 度 器 啟 動 時 會 被 重 新 初 始 化 為 0 :vTaskStartScheduler()->xPortStartScheduler()->uxCriticalNesting = 0。
如果 uxCriticalNesting 等于 1,即一層嵌套,要確保當(dāng)前沒有中斷活躍,即內(nèi)核外設(shè) SCB 中的中斷和控制寄存器 SCB_ICSR 的低 8 位要等于 0。
uxCriticalNesting這個變量比較重要,用于臨界段的嵌套計數(shù)。初學(xué)的同學(xué)也許會問這里直接的開關(guān)中斷不就可以了嗎,為什么還要做一個嵌套計數(shù)呢?主要是因為直接的開關(guān)中斷方式不支持在開關(guān)中斷之間的代碼里再次執(zhí)行開關(guān)中斷的嵌套處理,假如當(dāng)前們的代碼是關(guān)閉中斷的,嵌套了一個含有開關(guān)中斷的臨界區(qū)代碼后,退出時中斷就成開的了,這樣就出問題了。通過嵌套計數(shù)就有效地防止了用戶嵌套調(diào)用函數(shù)taskENTER_CRITICAL 和 taskEXIT_CRITICAL 時出錯。
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, baseprimsr basepri, ulNewBASEPRIdsbisb}return ulReturn; }通過上面的源碼實現(xiàn)可以看出,FreeRTOS 的開關(guān)全局中斷是通過操作寄存器 basepri 實現(xiàn)的。初學(xué)的同學(xué)也許會問,這里怎么沒有中斷嵌套計數(shù)了呢?是的,這里換了另外一種實現(xiàn)方法,通過保存和恢復(fù)寄存器 basepri 的數(shù)值就可以實現(xiàn)嵌套使用。如果大家研究過 uCOS-II 或者 III 的源碼,跟這里的實現(xiàn)方式是一樣的。
/*===========================================可屏蔽的中斷優(yōu)先級配置====================================================*/ /** 用于配置STM32的特殊寄存器basepri寄存器的值,用于屏蔽中斷,當(dāng)大于basepri值的優(yōu)先級的中斷將被全部屏蔽。basepri只有4bit有效,* 默認只為0,即全部中斷都沒有被屏蔽。configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY配置為:5,意思就是中斷優(yōu)先級大于5的中斷都被屏蔽。* 當(dāng)把配置好的優(yōu)先級寫到寄存器的時候,是按照8bit來寫的,所以真正寫的時候需要經(jīng)過轉(zhuǎn)換,公式為:* ((priority << (8 - __NVIC_PRIO_BITS)) & 0xff),其中的priority就是我們配置的真正的優(yōu)先級。經(jīng)過這個公式之后得到的是下面的這個宏:* configMAX_SYSCALL_INTERRUPT_PRIORITY** 在FreeRTOS中,關(guān)中斷是通過配置basepri寄存器來實現(xiàn)的,關(guān)掉的中斷由配置的basepri的值決定,小于basepri值的* 中斷FreeRTOS是關(guān)不掉的,這樣做的好處是系統(tǒng)設(shè)計者可以人為的控制那些非常重要的中斷不能被關(guān)閉,在緊要的關(guān)頭必須被響應(yīng)。* 而在UCOS中,關(guān)中斷是通過控制PRIMASK來實現(xiàn)的,PRIMASK是一個單1的二進制位,寫1則除能除了NMI和硬 fault的所有中斷。當(dāng)UCOS關(guān)閉* 中斷之后,即使是你在系統(tǒng)中設(shè)計的非常緊急的中斷來了都不能馬上響應(yīng),這加大了中斷延遲的時間,如果是性命攸關(guān)的場合,那后果估計挺嚴重。* 相比UCOS的關(guān)中斷的設(shè)計,FreeRTOS的設(shè)計則顯得人性化很多。**/ #define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 2 #define configMAX_SYSCALL_INTERRUPT_PRIORITY ( configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )退出臨界段
退出臨界段,不帶中斷保護版本且不能嵌套的代碼實現(xiàn)
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. 帶中斷保護的版本
/* ==========退出臨界段,帶中斷保護版本,可以嵌套=============== */ /* 在 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} }應(yīng)用
在 FreeRTOS 中,對臨界段的保護出現(xiàn)在兩種場合,一種是在中斷場合一種是在非中斷場合
/* 中斷場合*/ {uint32_t ulReturn;/* 進入臨界段 */ulReturn = taskENTER_CRITICAL_FROM_ISR();/* 臨界段代碼 *//* 退出臨界段 */taskEXIT_CRITICAL_FROM_ISR( ulReturn ); }/* 非中斷場合 */ {/* 進入臨界段 */taskENTER_CRITICAL();/* 臨界段代碼 *//* 退出臨界段*/taskEXIT_CRITICAL(); }參考鳴謝:
https://www.itdaan.com/blog/2017/07/21/24a1a4f2e6d7df68fbedf353e7096082.html
總結(jié)
以上是生活随笔為你收集整理的FreeRTOS内核详解(1) —— 临界段保护原理的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 如何查看一个现有的keil工程之前由什么
- 下一篇: 【手把手带你学nRF52832/nRF5