1. 程式人生 > >FreeRTOS筆記(十二)資源管理

FreeRTOS筆記(十二)資源管理

文章目錄

上一文連結:FreeRTOS筆記(十一)延遲中斷


  考慮完多工(中斷)之間的通訊後,還需要考慮多工(中斷)之間的資源訪問,因為資源往往是互斥使用的,比如印表機、串列埠等等,一個任務在使用的時候,不允許另一個任務去打斷,否則就會出現資訊不一致的情況,造成混亂

  而一般情況下資源的訪問步驟必須是連續的,比如在C語言上程式碼if(0 == a) a = -1;

,其實在CPU內部進行了若干個步驟:載入暫存器的值 → 與0比較 → 根據結果跳轉。如果在載入暫存器之後被打斷,而且a原來非0但是被修改為0,那麼再次重入的時候,本應該不去執行a = -1,但是卻執行了
Alt

01 - 互斥機制

  在FreeRTOS中,訪問資源是一段任務的程式碼,所以總是希望這段程式碼在執行的時候全程不要被打斷,或者即使打斷但是其它任務不能訪問同樣的資源。
  全程不被打斷其實過於苛刻,所以基本都是專注於能夠被打斷但是重入後依然沒有問題的設計。任務被打斷的原因有2個:中斷(外部)的到來、排程器中斷(內部)排程任務,可以簡述為外部中斷和任務中斷,所以只要解決這2個問題,就可以實現資源互斥訪問機制。
Alt

比如,因為原因都是中斷,所以最粗暴的方式就是直接關閉所有中斷(臨界區),這樣任務中斷和外部中斷都不能打斷這段程式碼,但是這種方法太粗暴,FreeRTOS應該要實時響應外部中斷,所以可以只關任務中斷而開啟外部中斷(掛起排程器),這樣還是有問題,系統有時候不能關閉任務切換,比如存在週期性任務,那麼就可以用一種申請-釋放的方式訪問資源(互斥量),申請的一方即使被打斷,後來者也不能訪問,可是申請-釋放的方式是公用的,容易導致死鎖和優先順序反轉,所以原則上不應該相信其它任務可以很好管理資源,於是專門派一個任務負責資源的分配和釋放(守護任務),其它任務不能公用,只能間接使用、

Alt

  經過以上內容,可以瞭解到其實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,意思是開啟所有中斷,所以臨界區就是提供一種最直接粗暴的方式去訪問資源。
Alt

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不再計數,表達系統心跳暫時停止,於是排程器也不會進行任務切換,可以回顧任務切換
Alt

1.3 - 互斥量

  互斥量不需要關閉任何中斷,它採用一種申請-釋放的方式去訪問資源,申請和釋放的其實不是資源,而是代表資源的鑰匙,在這種方式下,資源與一把鑰匙繫結,要想訪問資源,必須先拿到這把鑰匙,沒有鑰匙的只能等待前者釋放,所以即使擁有鑰匙的一方被打斷,後者也不能訪問資源
Alt

  FreeRTOS提供了互斥量的相關API,互斥量本質是一個長度為1的佇列,可以回顧佇列和通訊

API 功能
xSemaphoreCreateMutex() 建立互斥量
xSemaphoreGive() 任務狀態下給出訊號量
xSemaphoreTake() 任務狀態下得到訊號量
xSemaphoreTakeFromISR() 中斷狀態下給出訊號量
xSemaphoreTakeFromISR() 中斷狀態下得到訊號量
vSemaphoreDelete() 刪除訊號量

1.4 - 守護任務

  無論是臨界區、掛起排程器還是互斥量,它們的使用都帶來非常多的問題,核心原因是資源是公用的,資源的所有權和使用權都是公共的,任何一個任務都可以去直接操作,為了解決這個核心原因,可以把資源私有化,所有權和使用權都在一個任務A上面,其它任務只能間接去訪問,比如把要寫入的資料發給A,由A去寫入,把要讀的資料要求發給A,由A去讀然後返回資料等等
Alt

  守護任務的實現不需要什麼特性或者機制的支援,是一個協議,設定好任務的程式碼邏輯後就可以實現,它非常乾淨利落,把資源私有化後,阻塞等待其它任務的要求
  例如列印操作就是用守護任務實現的,任何想列印的任務只需要把資料以通訊的方式傳輸給守護任務,守護任務負責資料的排隊和正確邏輯,不需要任何的申請-釋放

02 - 互斥機制的區別

互斥機制 本質 優點 缺點
臨界區 關閉任務中斷和外部中斷 確保資源訪問不可能被打斷,資源訪問過程簡單 其餘任務停滯、外部中斷得不到響應
掛起排程器 關閉任務中斷 可以響應外部中斷,不能被其它任務打斷,資源訪問過程簡單 其餘任務停滯
互斥量 資源需要申請和釋放 不需要關中斷,資源訪問過程簡單 容易出現死鎖和優先順序反轉
守護任務 資源私有化,是一個協議 不再出現以上問題缺點 資源訪問過程複雜,間接訪問可能帶來速度和效率問題

03 - 總結

  • 資源一般需要互斥訪問,因此需要互斥機制
  • FreeRTOS可以實現4種互斥機制,臨界區、掛起排程器、互斥量和守護任務
  • 4種互斥機制各有優缺點,需要在不同的場合使用