FreeRTOS筆記(十二)資源管理
文章目錄
上一文連結:FreeRTOS筆記(十一)延遲中斷
考慮完多工(中斷)之間的通訊後,還需要考慮多工(中斷)之間的資源訪問,因為資源往往是互斥使用的,比如印表機、串列埠等等,一個任務在使用的時候,不允許另一個任務去打斷,否則就會出現資訊不一致的情況,造成混亂
而一般情況下資源的訪問步驟必須是連續的,比如在C語言上程式碼if(0 == a) a = -1;
a = -1
,但是卻執行了01 - 互斥機制
在FreeRTOS中,訪問資源是一段任務的程式碼,所以總是希望這段程式碼在執行的時候全程不要被打斷,或者即使打斷但是其它任務不能訪問同樣的資源。
全程不被打斷其實過於苛刻,所以基本都是專注於能夠被打斷但是重入後依然沒有問題的設計。任務被打斷的原因有2個:中斷(外部)的到來、排程器中斷(內部)排程任務,可以簡述為外部中斷和任務中斷,所以只要解決這2個問題,就可以實現資源互斥訪問機制。
比如,因為原因都是中斷,所以最粗暴的方式就是直接關閉所有中斷(臨界區),這樣任務中斷和外部中斷都不能打斷這段程式碼,但是這種方法太粗暴,FreeRTOS應該要實時響應外部中斷,所以可以只關任務中斷而開啟外部中斷(掛起排程器),這樣還是有問題,系統有時候不能關閉任務切換,比如存在週期性任務,那麼就可以用一種申請-釋放的方式訪問資源(互斥量),申請的一方即使被打斷,後來者也不能訪問,可是申請-釋放的方式是公用的,容易導致死鎖和優先順序反轉,所以原則上不應該相信其它任務可以很好管理資源,於是專門派一個任務負責資源的分配和釋放(守護任務),其它任務不能公用,只能間接使用、
經過以上內容,可以瞭解到其實FreeRTOS提供了很多特性用於實現互斥機制,分別是臨界區、掛起排程器、互斥量和守護任務,4中互斥各有優缺點,分別應用在不同的場合
1.1 - 臨界區
FreeRTOS的臨界區是關閉所有中斷(除掉電中斷、undef中斷等除外),指巨集 taskENTER_CRITICAL()
與taskEXIT_CRITICAL()
之間的程式碼區間,跟蹤原始碼
taskENTER_CRITICAL() #進入臨界區
portENTER_CRITICAL()
vPortEnterCritical()
uxCriticalNesting++; #計數加1,用於巢狀
portDISABLE_INTERRUPTS() #遮蔽中斷
vPortRaiseBASEPRI()
msr basepri, configMAX_SYSCALL_INTERRUPT_PRIORITY
portEXIT_CRITICAL() #退出臨界區
portEXIT_CRITICAL()
vPortExitCritical()
uxCriticalNesting--; #計數減1,用於巢狀
portENABLE_INTERRUPTS() #開啟中斷
vPortSetBASEPRI( 0 )
msr basepri, 0
對於taskENTER_CRITICAL()
,最後往basepri
暫存器中寫入configMAX_SYSCALL_INTERRUPT_PRIORITY
,這個值是在FreeRTOSConfig.h中配置的,代表最大的可管理中斷,詳細可以看配置檔案,在這裡,可以理解為關閉所有的中斷(除掉電中斷、undef中斷等除外),所以一旦呼叫taskENTER_CRITICAL()
,那麼任務就一直處於執行態,直到taskEXIT_CRITICAL()
的出現。
而taskEXIT_CRITICAL()
,最後是往basepri
暫存器中寫入0,意思是開啟所有中斷,所以臨界區就是提供一種最直接粗暴的方式去訪問資源。
1.2 - 掛起排程器
掛起排程器使得任務的執行過程不被其它任務打斷,但是可以被外部中斷打斷,FreeRTOS提供了掛起/喚醒排程器的API
API | 功能 |
---|---|
vTaskSuspendAll() | 掛起排程器 |
xTaskResumeAll() | 喚醒排程器 |
跟蹤原始碼
vTaskSuspendAll()
++uxSchedulerSuspended; #計數加1,用於巢狀和標記
xTaskResumeAll()
taskENTER_CRITICAL() #進入臨界區
--uxSchedulerSuspended #計數減1,用於巢狀和標記
prvAddTaskToReadyList() #調出就緒任務
taskEXIT_CRITICAL() #退出臨界區
vTaskSuspendAll()
掛起排程器只是簡單地加1計數,因為這個uxSchedulerSuspended
全域性變數會在Systick中斷中使用(具體到xTaskIncrementTick()
函式),如果uxSchedulerSuspended
不為0(掛起),那麼xTickCount
不再計數,表達系統心跳暫時停止,於是排程器也不會進行任務切換,可以回顧任務切換
1.3 - 互斥量
互斥量不需要關閉任何中斷,它採用一種申請-釋放的方式去訪問資源,申請和釋放的其實不是資源,而是代表資源的鑰匙,在這種方式下,資源與一把鑰匙繫結,要想訪問資源,必須先拿到這把鑰匙,沒有鑰匙的只能等待前者釋放,所以即使擁有鑰匙的一方被打斷,後者也不能訪問資源
FreeRTOS提供了互斥量的相關API,互斥量本質是一個長度為1的佇列,可以回顧佇列和通訊
API | 功能 |
---|---|
xSemaphoreCreateMutex() | 建立互斥量 |
xSemaphoreGive() | 任務狀態下給出訊號量 |
xSemaphoreTake() | 任務狀態下得到訊號量 |
xSemaphoreTakeFromISR() | 中斷狀態下給出訊號量 |
xSemaphoreTakeFromISR() | 中斷狀態下得到訊號量 |
vSemaphoreDelete() | 刪除訊號量 |
1.4 - 守護任務
無論是臨界區、掛起排程器還是互斥量,它們的使用都帶來非常多的問題,核心原因是資源是公用的,資源的所有權和使用權都是公共的,任何一個任務都可以去直接操作,為了解決這個核心原因,可以把資源私有化,所有權和使用權都在一個任務A上面,其它任務只能間接去訪問,比如把要寫入的資料發給A,由A去寫入,把要讀的資料要求發給A,由A去讀然後返回資料等等
守護任務的實現不需要什麼特性或者機制的支援,是一個協議,設定好任務的程式碼邏輯後就可以實現,它非常乾淨利落,把資源私有化後,阻塞等待其它任務的要求
例如列印操作就是用守護任務實現的,任何想列印的任務只需要把資料以通訊的方式傳輸給守護任務,守護任務負責資料的排隊和正確邏輯,不需要任何的申請-釋放
02 - 互斥機制的區別
互斥機制 | 本質 | 優點 | 缺點 |
---|---|---|---|
臨界區 | 關閉任務中斷和外部中斷 | 確保資源訪問不可能被打斷,資源訪問過程簡單 | 其餘任務停滯、外部中斷得不到響應 |
掛起排程器 | 關閉任務中斷 | 可以響應外部中斷,不能被其它任務打斷,資源訪問過程簡單 | 其餘任務停滯 |
互斥量 | 資源需要申請和釋放 | 不需要關中斷,資源訪問過程簡單 | 容易出現死鎖和優先順序反轉 |
守護任務 | 資源私有化,是一個協議 | 不再出現以上問題缺點 | 資源訪問過程複雜,間接訪問可能帶來速度和效率問題 |
03 - 總結
- 資源一般需要互斥訪問,因此需要互斥機制
- FreeRTOS可以實現4種互斥機制,臨界區、掛起排程器、互斥量和守護任務
- 4種互斥機制各有優缺點,需要在不同的場合使用