FreeRTOS--堆內存管理


因為項目需要,最近開始學習FreeRTOS,一開始有些緊張,因為兩個星期之前對於FreeRTOS的熟悉度幾乎為零,經過對FreeRTOS官網的例子程序的摸索,和項目中問題的解決,遇到了很多熟悉的身影,以前在Linux平台編程的經歷給了我一些十分有用的經驗,后悔當初沒能在第一家公司待下去,浪費了大好時光。好吧,現在還是潛下心來搞搞FreeRTOS吧。

后續都是一系列FreeRTOS相關的隨筆,先把FreeRTOS“聖經”--Mastering the FreeRTOS Real Time kernel -- A Hands On Tutorial Guide 20161204好好研讀,接連的幾個隨筆都是我從這本“聖經”中翻譯出來的。翻譯難免有所疏漏、詞不達意,大家湊合着看吧。

從FreeRTOS V9.0.0開始FreeRTOS應用程序可以完全用靜態分配內存,而沒有必要引入堆內存管理。

章節引言和范圍

前提

FreeRTOS是以C源文件的形式提供的,因此成為一名合格的C語言編程人員是使用FreeRTOS的必要條件,因而這個章節假定讀者熟悉以下概念:

  • C語言項目是如何構建的,包含不同的編譯和鏈接過程
  • 堆和棧分別是什么
  • 標准C庫的malloc()free()函數

動態內存分配以及它和FreeRTOS的關系

從FreeRTOS V9.0.0開始內核對象既可以在編譯的時候靜態分配,也可以在運行時動態分配。本書隨后的章節將會介紹以下內核對象:tasks, queues, semaphoresevent groups。為了盡可能讓FreeRTOS易於使用,這些內核對象並不是在編譯時靜態分配的,而是在運行時動態分配的。內核對象創建時FreeRTOS分配RAM而在內核對象刪除時釋放內存。這樣的策略減少了設計和計划上的努力,簡化了API,並且減少了RAM的占用。

動態內存分配是C語言編程的概念,而不是針對FreeRTOS或者多任務編程的概念。它和FreeRTOS是相關的,因為內核對象是動態分配的,並且通用編譯器提供的動態內存分配方案對於實時應用程序並不總是適合的。

內存可以使用標准C庫的malloc()free()函數來分配,但有可能不適合,或者恰當,因為下幾點原因:

  • 在小型嵌入式系統中並不總是可用的
  • 它們的實現可能非常的大,占據了相當大的一塊代碼空間
  • 他們幾乎都不是線程安全的
  • 它們並不是確定的,每次調用這些函數執行的時間可能都不一樣
  • 它們有可能產生碎片
  • 它們有可能打亂鏈接器的配置
  • 如果允許堆空間的生長方向覆蓋其他變量占據的內存,它們會成為debug的災難

動態內存分配的可選項

從FreeRTOS V9.0.0開始內核對象既可以在編譯時靜態分配也可以在運行時動態分配。如今FreeRTOS把內存分配放在可移植層。這是認識到不同的嵌入式操作有不同的動態內存管理方法和時間要求,因此單個的動態內存分配算法將只適合於應用程序的一個子集。同樣,從核心代碼庫中移除動態內存分配使得應用程序編寫者提供自己的特定的實現,如果適合的話。

當FreeRTOS需要RAM的時候,並不是調用malloc(),而是調用pvPortMalloc()。當需要釋放RAM的時候,並不是調用free(),而是調用vPortFree()pvPortMalloc()和標准C庫的malloc()有同樣的函數原型,vPortFree()和標准C庫的free()有同樣的函數原型。

pvPortMalloc()vPortFree()都是公共函數,因此能夠被應用代碼調用。

FreeRTOS對於pvPortMalloc()vPortFree()提供了5種實現,后續章節會講到。FreeRTOS應用程序可以使用其中的一種,或者使用自己的實現。5種實現分別在heap_1.c, heap_2.c, heap_3.c, heap_4.cheap_5.c文件中,都存在於文件夾 FreeRTOS/Source/portable/MemMang 下。

范圍

本章節致力於讓讀者深入理解:

  • FreeRTOS何時分配RAM
  • FreeRTOS 提供的5種內存分配方案
  • 選用哪一種內存分配方案

內存分配方案示例

Heap_4 (其他幾種暫不去了解)###

heap_1, heap_2 一樣,heap_4也是把數組切割成更小的塊。和前面一樣,數組是靜態聲明的,由宏configTOTAL_HEAP_SIZE指定大小,所以這就使得即便數組中的內存還沒有被分配出去就讓應用程序顯得消耗了大量的RAM。

Heap_4使用了最先適應算法來分配內存。和heap_2不同,heap_4把臨近的空閑的存儲空間拼湊成一個更大的內存塊,這就減少了內存碎片化的風險。

最先適應算法確保了pvPortMalloc()使用第一塊空閑的足夠大的內存來滿足要申請的字節數。考慮下面的情景:

  • 堆里有3塊空閑內存塊,它們的大小分別是5個字節,200個字節,100個字節
  • 調用pvPortMalloc()來申請20個字節的RAM
    滿足字節數要求的第一塊空閑RAM塊是200個字節的RAM塊,因此pvPortMalloc()把大小為200個字節的RAM塊分割成兩塊,一塊是20個字節,一塊是180個字節,然會返回一個指向20個字節的指針。新的180個字節大小的RAM塊將在后續的pvPortMalloc()調用中可用。

Figure 7 演示了 heap_4 最先適應算法如何拼接內存,同樣也演示了內存的分配和釋放:

Figure 7

  1. A演示了創建3個任務之后的數組的樣子,一大塊空的塊存在於數組的頂端。
  2. B演示了刪除1個任務之后的數組,一大塊空的塊存在於數組的頂端。被刪除的那個任務占據的TCB和棧存儲空間現在是空的,並且它們拼接成一個大的空的塊。
  3. C演示了FreeRTOS創建了一個Queue。隊列是通過xQueueCreate() API 創建的,它是調用pvPortMalloc() 來分配存儲空間的。由於heap_4采用最先適應算法,pvportMalloc()將會使用第一塊大的足夠容納隊列的RAM塊來分配,在Figure 7中就采用之前刪除任務的那一塊。然而隊列並不完全消耗那個空閑的區塊,所以那個RAM塊會分成兩個部分,未使用的部分將會由后續的pvPortMalloc()占用。
  4. D演示了應用程序直接調用pvPortMalloc()而不是間接地由FreeRTOS API調用之后的情形。用戶分配的區塊足夠小,能夠放在第一個空閑的區塊中,這個區塊就是隊列占用的區塊和后面的TCB占用的區塊之間的那一塊。
    刪除任務釋放的內存,現在被分割成3個區塊,第一個區塊是隊列,第二個區塊是用戶分配的,第三個區塊還是空的。
  5. E 演示了隊列刪除之后,存儲空間也自動釋放了。現在用戶分配的區塊兩邊都是空閑區塊。
  6. F 演示了用戶分配的存儲空間釋放的情形。這個區塊現在和兩邊的空閑區塊拼接成了一個更大的空閑區塊。

Heap_4並不是確定性的,但是要比標准庫函數實現的malloc()free()運行的更快。

設定Heap_4數組的起始地址

此章節包含更高階的信息,僅僅為了使用Heap_4是沒有必要閱讀和理解此章節的。

某些時候應用程序開發者需要指定heap_4數組的起始地址位於某個特定的內存。例如,FreeRTOS 任務的棧是從堆中分配的,就有可能有必要保證堆是分配在快速的內存中,而不是慢速的外存。

默認情況下,heap_4數組是在heap_4.c源文件中聲明的,它的起始地址是由鏈接器自動確定的。然而,如果在文件FreeRTOSConfig.h中把編譯時配置選項configAPPLICATION_ALLOCATED_HEAP設為常量1,那么數組必須由使用FreeRTOS的應用聲明。如果把數組聲明為應用的一部分,那么應用編寫者可以指定數組的起始地址。

如果把文件FreeRTOSConfig.h中的configAPPLICATION_ALLOCATED_HEAP設定為1,那么應用程序源文件中必須聲明一個名字為ucHeapuint8_t類型的數組,它的大小有configTOTAL_HEAP_SIZE設定。

把變量放在某個內存地址的語法取決於使用了哪種編譯器,下面演示了兩種編譯器的用法:

  1. Listing 2演示的是GCC編譯器聲明數組並把數組放在名字為.my_heap的段中。
  2. Listing 3演示的是IAR編譯器把數組放在內存絕對地址0x20000000上。

uint8_t ucHeap [configTOTAL_HEAP_SIZE] attribute (( section(".my_heap") ));

Listing 2

uint8_t ucHeap [configTOTAL_HEAP_SIZE] @ 0x20000000;

Listing 3

和堆相關的實用函數

xPortGetFreeHeapSize() API

這個函數可以獲取調用時堆中空閑內存的大小,以字節為單位。使用它可以優化堆的大小。例如,當內核對象都創建完畢后調用xPortGetFreeHeapSize()返回2000,那么可以把configTOTAL_HEAP_SIZE減小2000.

需要注意,當使用heap_3時是不能調用這個函數的。

xPortGetMinimumEverFreeHeapSize() API

此函數返回FreeRTOS應用程序開始運行之后曾經存在的最小的未被分配的存儲空間的字節數。它的返回值指示了應用程序離將要耗盡堆空間的接近程度。例如xPortGetMinimunEverFreeHeapSize()返回200個字節,那么從應用程序開始運行之后的某個時間,在使用200個字節就會把堆空間用完。

需要注意,xPortGetMinimumEverFreeHeapSize()只在使用heap_4或者heap_5時生效。

Malloc 失敗鈎子函數

應用程序可以直接調用pvPortMalloc()。當然在FreeRTOS源文件中每當內核對象創建時也會調用這個函數。此類的內核對象包括任務,隊列,信號量和事件組。

和標准庫函數malloc()一樣,如果pvPortMalloc()因為申請RAM的大小不能滿足沒能返回一塊RAM空間就會返回NULL。如果編程人員調用pvPortMalloc()來創建內核對象,但是返回NULL就說明內核對象沒有創建成功。

例子中的所有堆分配方案都可以給pvPortMalloc()配置一個鈎子函數(也稱作回調函數),當pvPortMalloc()返回NULL時調用這個鈎子函數。

如果文件FreeRTOSConfig.h中的configUSE_MALLOC_FAILED_HOOK設置為1,那么應用程序必須提供一個內存分配失敗時的鈎子函數,它的名字和原型參見如下。只要對這個應用來說是合適的,這個鈎子函數可以用任何方法來實現。

void vApplicationMallocFailedHook( void );

聲明

歡迎轉載,請注明出處和作者,同時保留聲明。
作者:LinTeX9527
出處:http://www.cnblogs.com/LinTeX9527/p/8007541.html
本博客的文章如無特殊說明,均為原創,轉載請注明出處。如未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責任的權利。


免責聲明!

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



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