STM32記憶體結構介紹和FreeRTOS記憶體分配技巧

撲魚 發表於 2021-04-06

這是我第一次使用FreeRTOS構建STM32的專案,踩了好些坑,又發現了我缺乏對於作業系統的記憶體及其空間的分配的知識,故寫下文件記錄學習成果。

文章最後要解決的問題是,如何恰當地分配FreeRTOS中的堆、任務棧的空間。但是在概念的理解上,也需要知道STM32記憶體的相關知識。所以首先大致介紹一下STM32的記憶體結構。

STM32記憶體結構

STM32的資料在物理上分別儲存在RAM和Flash中。RAM可讀可寫,掉電清零。Flash可讀不可寫,但能掉電儲存,並且一般空間比RAM大很多。

在關於如何使用RAM和Flash的問題上,STM32的記憶體又有了6個儲存資料段和3種儲存屬性區的概念。STM32記憶體結構介紹和FreeRTOS記憶體分配技巧

6個儲存資料段

data

資料段,儲存已初始化的,且初始化不為0的全域性變數和靜態變數。

bss

Block Started by Symbol。儲存未初始化的,或初始化為0的全域性變數和靜態變數。

text

程式碼段,儲存程式程式碼。

constdata

儲存只讀常量。

heap

堆,存放程式執行中被動態分配的記憶體段。其可用大小定義在啟動檔案startup_stm32fxx.s中,由程式設計師使用malloc()free()函式進行分配和釋放。

stack

棧,其大小定義在啟動檔案startup_stm32fxx.s中,由系統自動分配和釋放。可存放區域性變數、函式的引數和返回值,中斷髮生時能儲存現場。但是static宣告的區域性靜態變數不儲存在棧中,而是放在data資料段。

3種儲存屬性區

RO(Read Only)

燒寫到Flash中,可以長久儲存。text程式碼段和constdata都屬於RO。由於需要掉電儲存,RO裡也儲存了一份data的資料。

RW(Read Write)

儲存在RAM中。data屬於此區。上電時微控制器會將Flash中儲存的data型別資料複製到RAM中,以供讀寫使用。

ZI(Zero Init)

零初始化區,同樣儲存在RAM裡。系統上電時會把此區域的資料進行0初始化。bss,heap,stack均屬於這個區域。

小結

STM32的RAM上有RW和ZI兩個屬性區,裡邊包含了data,bss,堆(heap),棧(stack)這幾個資料段。這裡是程式執行的所在。

Flash中有RO區,包含了text、constdata和data三個段,這裡則是程式本體所在。

FreeRTOS中的堆

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的空間減去已分配的空間,即為能給堆分配的最大空間:

\[Space = RAM - bss - data - SysHeap - Stack \]

FreeRTOS堆和任務空間分配技巧

FreeRTOS堆和任務棧在執行中具有很強的動態性,其大小很難估計。

我們在實際使用中,可以先把空間調整得大一些。程式正常執行後,再通過一些API檢視堆疊剩餘的空間大小,估算程式執行中需求記憶體空間的最大值。最後將這個最大值乘一個安全係數,得到最終應該分配的空間大小。安全係數推薦1.3到1.5。

檢視堆(heap)剩餘空間的API有:

size_t xPortGetFreeHeapSize( void );	         //獲取當前未分配的記憶體堆大小
size_t xPortGetMinimumEverFreeHeapSize( void );	 //獲取未分配的記憶體堆歷史最小值

它們返回值的單位都是位元組。

需要注意的是,xPortGetFreeHeapSize() 在使用heap_3.c時不能被呼叫;xPortGetMinimumEverFreeHeapSize()則只能在使用heap_4.cheap_5.c時生效。

FreeRTOS中也有檢視任務棧剩餘空間的API:

UBaseType_t uxTaskGetStackHighWaterMark( TaskHandle_t xTask );

這個函式可以獲取一個任務從建立好到呼叫此函式時,任務棧空間的歷史最小剩餘值(HighWaterMark)。使用這個函式時需注意,它的返回值的單位是字(STM32裡1個字長為4個位元組)。

這個API預設是關閉狀態,需要手動在Cubemx(或配置檔案中)將巨集INCLUDE_uxTaskGetStackHighWaterMark置為1。

我在使用過這些API後發現,他們本身也會佔用相當的記憶體空間,尤其是uxTaskGetStackHighWaterMark(),會拖慢任務執行速度。所以在程式的正式版中,應該將他們刪除。