這是我第一次使用FreeRTOS構建STM32的項目,踩了好些坑,又發現了我缺少對於操做系統的內存及其空間的分配的知識,故寫下文檔記錄學習成果。程序員
文章最後要解決的問題是,如何恰當地分配FreeRTOS中的堆、任務棧的空間。可是在概念的理解上,也須要知道STM32內存的相關知識。因此首先大體介紹一下STM32的內存結構。安全
STM32的數據在物理上分別儲存在RAM和Flash中。RAM可讀可寫,掉電清零。Flash可讀不可寫,但能掉電儲存,而且通常空間比RAM大不少。函數
在關於如何使用RAM和Flash的問題上,STM32的內存又有了6個儲存數據段和3種儲存屬性區的概念。學習
數據段,儲存已初始化的,且初始化不爲0的全局變量和靜態變量。操作系統
Block Started by Symbol。儲存未初始化的,或初始化爲0的全局變量和靜態變量。3d
代碼段,儲存程序代碼。code
儲存只讀常量。blog
堆,存放進程運行中被動態分配的內存段。其可用大小定義在啓動文件startup_stm32fxx.s
中,由程序員使用malloc()
和free()
函數進行分配和釋放。隊列
棧,其大小定義在啓動文件startup_stm32fxx.s
中,由系統自動分配和釋放。可存放局部變量、函數的參數和返回值,中斷髮生時能保存現場。可是static聲明的局部靜態變量不儲存在棧中,而是放在data數據段。進程
燒寫到Flash中,能夠長久保存。text代碼段和constdata都屬於RO。因爲須要掉電儲存,RO裏也保存了一份data的數據。
儲存在RAM中。data屬於此區。上電時單片機會將Flash中保存的data類型數據複製到RAM中,以供讀寫使用。
零初始化區,一樣儲存在RAM裏。系統上電時會把此區域的數據進行0初始化。bss,heap,stack均屬於這個區域。
STM32的RAM上有RW和ZI兩個屬性區,裏邊包含了data,bss,堆(heap),棧(stack)這幾個數據段。這裏是程序運行的所在。
Flash中有RO區,包含了text、constdata和data三個段,這裏則是程序本體所在。
FreeRTOS中的堆也屬於ZI區,可是它與STM32內存結構中的堆並不佔用相同的空間,兩個堆同時存在。如下出現的堆(heap)表示FreeRTOS堆,另外在STM32啓動文件中定義大小的堆稱爲系統堆。
FreeRTOS內核主要使用的內存管理函數爲:
void *pvPortMalloc( size_t xSize ); //申請內存 void vPortFree( void *pv ); //釋放內存
以上函數控制的是FreeRTOS堆;系統堆則應使用malloc()
和free()
來分配和釋放。
FreeRTOS有5種heap的實現方式,在STM32CubeMX中默認爲heap_4.c
。這種方式能夠知足大部分使用需求,暫時不用關注其實現細節。
這一個堆的大小定義在FreeRTOSConfig.c
中:
#define configTOTAL_HEAP_SIZE ((size_t)3072)
FreeRTOS建立任務時默認的任務棧大小爲128字,在32位系統中即爲128*4=512Byte,再加上TCB塊佔用84Byte,一共596Byte。而大小爲3072Byte的堆容許建立3個這樣的任務,佔用約1800Byte。堆中剩餘的部分則存放了系統內核、信號量、隊列、任務通知等數據。
須要建立更多任務時,堆的大小可自行修改。用RAM的空間減去已分配的空間,即爲能給堆分配的最大空間:
FreeRTOS堆和任務棧在運行中具備很強的動態性,其大小很難估計。
咱們在實際使用中,能夠先把空間調整得大一些。程序正常運行後,再經過一些API查看堆棧剩餘的空間大小,估算程序運行中需求內存空間的最大值。最後將這個最大值乘一個安全係數,獲得最終應該分配的空間大小。安全係數推薦1.3到1.5。
查看堆(heap)剩餘空間的API有:
size_t xPortGetFreeHeapSize( void ); //獲取當前未分配的內存堆大小 size_t xPortGetMinimumEverFreeHeapSize( void ); //獲取未分配的內存堆歷史最小值
它們返回值的單位都是字節。
須要注意的是,xPortGetFreeHeapSize()
在使用heap_3.c
時不能被調用;xPortGetMinimumEverFreeHeapSize(
)則只能在使用heap_4.c
或heap_5.c
時生效。
FreeRTOS中也有查看任務棧剩餘空間的API:
UBaseType_t uxTaskGetStackHighWaterMark( TaskHandle_t xTask );
這個函數能夠獲取一個任務從建立好到調用此函數時,任務棧空間的歷史最小剩餘值(HighWaterMark)。使用這個函數時需注意,它的返回值的單位是字(STM32裏1個字長爲4個字節)。
這個API默認是關閉狀態,須要手動在Cubemx(或配置文件中)將宏INCLUDE_uxTaskGetStackHighWaterMark
置爲1。
我在使用過這些API後發現,他們自己也會佔用至關的內存空間,尤爲是uxTaskGetStackHighWaterMark()
,會拖慢任務運行速度。因此在程序的正式版中,應該將他們刪除。