FreeRTOS 臨界段和開關中斷


以下轉載自安富萊電子: http://forum.armfly.com/forum.php

臨界段
代碼的臨界段也稱為臨界區,一旦這部分代碼開始執行,則不允許任何中斷打斷。為確保臨界段代碼
的執行不被中斷,在進入臨界段之前須關中斷,而臨界段代碼執行完畢后,要立即開中斷。
FreeRTOS 臨界段相關知識補充
FreeRTOS 的源碼中有多處臨界段的地方, 臨界段雖然保護了關鍵代碼的執行不被打斷, 但也會
影響系統的實時性。比如此時某個任務正在調用系統 API 函數,而且此時中斷正好關閉了,也就是進
入到了臨界區中,這個時候如果有一個緊急的中斷事件被觸發,這個中斷就不能得到及時執行,必須
等到中斷開啟才可以得到執行, 如果關中斷時間超過了緊急中斷能夠容忍的限度, 危害是可想而知的。


FreeRTOS 源碼中就有多處臨界段的處理,跟 FreeRTOS 一樣,uCOS-II 和 uCOS-III 源碼中都是有
臨界段的,而 RTX 的源碼中不存在臨界段。 另外,除了 FreeRTOS 操作系統源碼所帶的臨界段以外,用
戶寫應用的時候也有臨界段的問題,比如以下兩種:
讀取或者修改變量(特別是用於任務間通信的全局變量)的代碼,一般來說這是最常見的臨界代碼。
調用公共函數的代碼,特別是不可重入的函數,如果多個任務都訪問這個函數,結果是可想而知的。
總之,對於臨界段要做到執行時間越短越好,否則會影響系統的實時性。

任務代碼臨界段處理
FreeRTOS 任務代碼中臨界段的進入和退出主要是通過操作寄存器 basepri 實現的。進入臨界段前操
作寄存器 basepri 關閉了所有小於等於宏定義 configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY
所定義的中斷優先級,這樣臨界段代碼就不會被中斷干擾到,而且實現任務切換功能的 PendSV 中斷和滴
答定時器中斷是最低優先級中斷,所以此任務在執行臨界段代碼期間是不會被其它高優先級任務打斷的。
退出臨界段時重新操作 basepri 寄存器,即打開被關閉的中斷這里我們不考慮不受 FreeRTOS 管理的更
高優先級中斷。 FreeRTOS 進入和退出臨界段的函數如下:

 #define taskENTER_CRITICAL() portENTER_CRITICAL()
#define taskEXIT_CRITICAL() portEXIT_CRITICAL()
上面這兩個函數是供用戶調用的,其中函數 taskENTER_CRITICAL 是進入臨界段,函數
taskEXIT_CRITICAL 是退出臨界段。 進一步跟蹤宏定義的實現如下:
#define portENTER_CRITICAL() vPortEnterCritical()
#define portEXIT_CRITICAL() vPortExitCritical()
再進一步跟蹤宏定義的實現如下:

通過上面的兩個函數 vPortEnterCritical 和 vPortExitCritical 可以看出,進入臨界段和退出臨界段是通過
函數調用開關中斷函數 portENABLE_INTERRUPTS 和 portDISABLE_INTERRUPTS 實現的。 細心的讀者
還會發現上面的這兩個函數都對變量 uxCriticalNesting 進行了操作。這個變量比較重要,用於臨界段的
嵌套計數。初學的同學也許會問這里直接的開關中斷不就可以了嗎,為什么還要做一個嵌套計數呢?主要
是因為直接的開關中斷方式不支持在開關中斷之間的代碼里再次執行開關中斷的嵌套處理,假如當前我們
的代碼是關閉中斷的,嵌套了一個含有開關中斷的臨界區代碼后,退出時中斷就成開的了,這樣就出問題
了。 通過嵌套計數就有效地防止了用戶嵌套調用函數 taskENTER_CRITICAL 和 taskEXIT_CRITICAL 時出錯。

 

 

通過上面的源碼實現可以看出,FreeRTOS 的開關全局中斷是通過操作寄存器 basepri 實現的,關於這個寄存器,我們已經在前面進行了詳細的講解,這里不再贅述。 
使用的時候一定要保證成對使用 。

中斷服務程序臨界段處理
與任務代碼里臨界段的處理方式類似,中斷服務程序里面臨界段的處理也有一對開關中斷函數。
#define taskENTER_CRITICAL_FROM_ISR() portSET_INTERRUPT_MASK_FROM_ISR()
#define taskEXIT_CRITICAL_FROM_ISR( x ) portCLEAR_INTERRUPT_MASK_FROM_ISR( x )
進一步跟蹤宏定義的實現如下:
#define portSET_INTERRUPT_MASK_FROM_ISR() ulPortRaiseBASEPRI()
#define portCLEAR_INTERRUPT_MASK_FROM_ISR(x) vPortSetBASEPRI(x)
再進一步跟蹤宏定義的實現如下:

 

通過上面的源碼可以看出,中斷服務程序里面的臨界段代碼的開關中斷也是通過寄存器 basepri 實現的。
初學的同學也許會問,這里怎么沒有中斷嵌套計數了呢?是的,這里換了另外一種實現方法,通過保存和
恢復寄存器 basepri 的數值就可以實現嵌套使用。如果大家研究過 uCOS-II 或者 III 的源碼,跟這里的實
現方式是一樣的,具體看下面的使用舉例。

使用舉例:
使用的時候一定要保證成對使用

開關中斷的實現
FreeRTOS 也專門提供了一組開關中斷函數,實現比較簡單,其實就是前面 15.2 小節里面臨界段進
入和退出函數的精簡版本,主要區別是不支持中斷嵌套。 具體實現如下:
#define taskDISABLE_INTERRUPTS() portDISABLE_INTERRUPTS()
#define taskENABLE_INTERRUPTS() portENABLE_INTERRUPTS()
進一步跟蹤宏定義的實現如下:
#define portDISABLE_INTERRUPTS() vPortRaiseBASEPRI() 

#define portENABLE_INTERRUPTS() vPortSetBASEPRI( 0 )

函數 vPortRaiseBASEPRI 和 vPortSetBASEPRI 的源碼實現如下:

從上面的源碼可以看出, FreeRTOS 的全局中斷開關是通過操作寄存器 basepri 實現的,關於這個寄存器,
我們已經在前面進行了詳細的講解,這里不再贅述。

使用的時候一定要保證成對使用


BSP 板級支持包中開關中斷的特別處理
前面為大家講解了 FreeRTOS 臨界段的處理方法和開關中斷方法,加上了 FreeRTOS 操作系統后,我
們實際編寫的外設驅動又該怎么修改呢?因為外設驅動編寫時,有些地方有用到開關中斷操作,這里以此
教程配套的 STM32F103,F407 和 F429 開發板為例進行說明,這三種開發板的外設驅動的編寫架構都是
統一的,用戶只需將 bsp.h 文件里面的宏定義:

#define USE_FreeRTOS 1
將中斷開關設置改成了條件編譯的形式,這樣在使用裸機或者使用 FreeRTOS 時,切換自如。此宏定
義配置為 1 表示使用 FreeRTOS 的開關中斷 API 函數,配置為 0 表示使用裸機的方式開關中斷。
采用 taskENTER_CRITICAL()和 taskEXIT_CRITICAL()實現開關中斷
因為 BSP 驅動包的源碼基本沒有在中斷里面進行開關中斷,都是在中斷以外,所以開關中斷是采用的
任務代碼里面臨界段的處理函數,而且支持嵌套調用。
大家寫的工程代碼也可以采用類似的方案,方便裸機和 FreeRTOS 的切換,或者采用其它適合自己的
方案。 另外要注意,因為 FreeRTOS 存在不受其控制的更高優先級中斷,用戶需要根據實際情況進行特別
處理,可以不采用 FreeRTOS 的開關中斷函數,而是直接使用__set_PRIMASK 實現全局中斷的開關。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM