1. 程式人生 > >FreeRTOS記憶體管理

FreeRTOS記憶體管理

FreeRTOS提供了幾個記憶體堆管理方案,有複雜的也有簡單的。其中最簡單的管理策略也能滿足很多應用的要求,比如對安全要求高的應用,這些應用根本不允許動態記憶體分配的。

      FreeRTOS也允許你自己實現記憶體堆管理,甚至允許你同時使用兩種記憶體堆管理方案。同時實現兩種記憶體堆允許任務堆疊和其它RTOS物件放置到快速的內部RAM,應用資料放置到低速的外部RAM。

      每當建立任務、佇列、互斥量、軟體定時器、訊號量或事件組時,RTOS核心會為它們分配RAM。標準函式庫中的malloc()和free()函式有些時候能夠用於完成這個任務,但是:

  1. 在嵌入式系統中,它們並不總是可以使用的;
  2. 它們會佔用更多寶貴的程式碼空間;
  3. 它們沒有執行緒保護;
  4. 它們不具有確定性(每次呼叫執行的時間可能會不同);

      因此,提供一個替代的記憶體分配方案通常是必要的。

      嵌入式/實時系統具有千差萬別的RAM和時間要求,因此一個RAM記憶體分配演算法可能僅屬於一個應用的子集。

      為了避免這個問題,FreeRTOS在移植層保留記憶體分配API函式。移植層在RTOS核心程式碼原始檔之外(不屬於核心原始碼),這使得不同的應用程式可以提供適合自己的應用實現。當RTOS核心需要RAM時,呼叫pvPortMallo()函式來代替malloc()函式。當RAM要被釋放時,呼叫vPortFree()函式來代替free()函式。

      FreeRTOS下載包中提供5種簡單的記憶體分配實現,本文稍後會進行描述。使用者可以適當的選擇其中的一個,也可以自己設計記憶體分配策略。

      FreeRTOS提供的記憶體分配方案分別位於不同的原始檔(heap_1.c、heap_2.c、heap_3.c、heap_4.c、heap_5.c)之中,原始檔位於下載包\FreeRTOS\Source\portable\MemMang資料夾中。其它實現方法可以根據需要增加。如果要使用FreeRTOS提供的記憶體堆分配方案,選中的原始檔必須被正確的包含到工程檔案中。

1.heap_1.c

      這是所有實現中最簡單的一個。一旦分配記憶體之後,它甚至不允許釋放分配的記憶體。儘管這樣,heap_1.c還是適用於大部分嵌入式應用程式。這是因為大多數深度嵌入式(deeplyembedded)應用只是在系統啟動時建立所有任務、佇列、訊號量等,並且直到程式結束都會一直使用它們,永遠不需要刪除。

當需要分配RAM時,這個記憶體分配方案只是簡單的將一個大陣列細分出一個子集來。大陣列的容量大小通過FreeRTOSConfig.h檔案中的configTOTAL_HEAP_SIZE巨集來設定。

API函式xPortGetFreeHeapSize()返回未分配的堆疊空間總大小,可以通過這個函式返回值對configTOTAL_HEAP_SIZE進行合理的設定。

      heap_1功能簡介:

  • 用於從不會刪除任務、佇列、訊號量、互斥量等的應用程式(實際上大多數使用FreeRTOS的應用程式都符合這個條件)
  • 執行時間是確定的並且不會產生記憶體碎片
  • 實現和分配過程非常簡單,需要的記憶體是從一個靜態陣列中分配的,意味著這種記憶體分配通常只是適用於那些不進行動態記憶體分配的應用。

2.heap_2.c

      和方案1不同,這個方案使用一個最佳匹配演算法,它允許釋放之前分配的記憶體塊。它不會把相鄰的空閒塊合成一個更大的塊(換句話說,這會造成記憶體碎片)。

有效的堆疊空間大小由位於FreeRTOSConfig.h檔案中的configTOTAL_HEAP_SIZE巨集來定義。

      API函式xPortGetFreeHeapSize()返回剩下的未分配堆疊空間的大小(可用於優化設定configTOTAL_HEAP_SIZE巨集的值),但是不能提供未分配記憶體的碎片細節資訊。

      heap_2功能簡介:

  • 可以用於重複的分配和刪除具有相同堆疊空間的任務、佇列、訊號量、互斥量等等,並且不考慮記憶體碎片的應用程式。
  • 不能用在分配和釋放隨機位元組堆疊空間的應用程式
    • 如果一個應用程式動態的建立和刪除任務,並且分配給任務的堆疊空間總是同樣大小,那麼大多數情況下heap_2.c是可以使用的。但是,如果分配給任務的堆疊不總是相等,那麼釋放的有效記憶體可能碎片化,形成很多小的記憶體塊。最後會因為沒有足夠大的連續堆疊空間而造成記憶體分配失敗。在這種情況下,heap_4.c是一個很好的選擇。
    • 如果一個應用程式動態的建立和刪除佇列,並且在每種情況下佇列儲存區域(佇列儲存區域指佇列項數目乘以每個佇列長度)都是同樣的,那麼大多數情況下heap_2.c可以使用。但是,如果佇列儲存區在每種情況下並不總是相等,那麼釋放的有效記憶體可能碎片化,形成很多小的記憶體塊。最後會因為沒有足夠大的連續堆疊空間而造成記憶體分配失敗。在這種情況下,heap_4.c是一個很好的選擇。
    • 應用程式直接呼叫pvPortMalloc() 和 vPortFree()函式,而不僅是通過FreeRTOS API間接呼叫。
  • 如果你的應用程式中的佇列、任務、訊號量、互斥量等等處在一個不可預料的順序,則可能會導致記憶體碎片問題,雖然這是小概率事件,但必須牢記。
  • 不具有確定性,但是它比標準庫中的malloc函式具有高得多的效率。

      heap_2.c適用於需要動態建立任務的大多數小型實時系統(smallreal time)。

3.heap_3.c

      heap_3.c簡單的包裝了標準庫中的malloc()和free()函式,包裝後的malloc()和free()函式具備執行緒保護。

      heap_3.c功能簡介:

  • 需要連結器設定一個堆疊,並且編譯器庫提供malloc()和free()函式。
  • 不具有確定性
  • 可能明顯的增大RTOS核心的程式碼大小

      注:使用heap_3時,FreeRTOSConfig.h檔案中的configTOTAL_HEAP_SIZE巨集定義沒有作用。

4.heap_4.c

      這個方案使用一個最佳匹配演算法,但不像方案2那樣。它會將相鄰的空閒記憶體塊合併成一個更大的塊(包含一個合併演算法)。

      有效的堆疊空間大小由位於FreeRTOSConfig.h檔案中的configTOTAL_HEAP_SIZE來定義。

      API函式xPortGetFreeHeapSize()返回剩下的未分配堆疊空間的大小(可用於優化設定configTOTAL_HEAP_SIZE巨集的值),但是不能提供未分配記憶體的碎片細節資訊。

      heap_4.c功能簡介:

  • 可用於重複分配、刪除任務、佇列、訊號量、互斥量等等的應用程式。
  • 可以用於分配和釋放隨機位元組記憶體的情況,並不像heap_2.c那樣產生嚴重碎片。
  • 不具有確定性,但是它比標準庫中的malloc函式具有高得多的效率。

      heap_4.c還特別適用於移植層程式碼,可以直接使用pvPortMalloc()和 vPortFree()函式來分配和釋放記憶體。

5.heap_5.c(V8.1.0新增)

      這個方案同樣實現了heap_4.c中的合併演算法,並且允許堆疊跨越多個非連續的記憶體區。

      Heap_5通過呼叫vPortDefineHeapRegions()函式實現初始化,在該函式執行完成前不允許使用記憶體分配和釋放。建立RTOS物件(任務、佇列、訊號量等等)會隱含的呼叫pvPortMalloc(),因此必須注意:使用heap_5建立任何物件前,要先執行vPortDefineHeapRegions()函式。

      vPortDefineHeapRegions()函式只需要單個引數。該引數是一個HeapRegion_t結構體型別陣列。HeapRegion_t在portable.h中定義,如下所示:

  typedef struct HeapRegion  
  {  
      /* 用於記憶體堆的記憶體塊起始地址*/  
      uint8_t *pucStartAddress;  
    
      /* 記憶體塊大小 */  
      size_t xSizeInBytes;  
  } HeapRegion_t;

      這個陣列必須使用一個NULL指標和0位元組元素作為結束,起始地址必須從小到大排列。下面的程式碼段提供一個例子。MSVCWin32模擬器演示例程使用了heap_5,因此可以當做一個參考例程。

 /* 在記憶體中為記憶體堆分配兩個記憶體塊.第一個記憶體塊0x10000位元組,起始地址為0x80000000, 
 第二個記憶體塊0xa0000位元組,起始地址為0x90000000.起始地址為0x80000000的記憶體塊的 
 起始地址更低,因此放到了陣列的第一個位置.*/  
 const HeapRegion_t xHeapRegions[] =  
 {  
     { ( uint8_t * ) 0x80000000UL, 0x10000 },  
     { ( uint8_t * ) 0x90000000UL, 0xa0000 },  
     { NULL, 0 } /* 陣列結尾. */  
 };  
   
 /* 向函式vPortDefineHeapRegions()傳遞陣列引數. */  
 vPortDefineHeapRegions( xHeapRegions );