FreeRTOS---堆内存管理(一)
FreeRTOS的堆內(nèi)存管理
- 簡介
- 動態(tài)內(nèi)存分配及其與 FreeRTOS 的相關(guān)性
- 動態(tài)內(nèi)存分配選項
- 內(nèi)存分配方案
- Heap_1
- heap_2
- Heap_3
- Heap_4
- 設(shè)置heap_4的起始地址
- Heap_5
- vPortDefineHeapRegions()
- 堆相關(guān)的函數(shù)
- xPortGetFreeHeapSize
- xPortGetMinimumEverFreeHeapSize
- Malloc調(diào)用失敗的Hook函數(shù)
這篇文章先說原理,下一遍文章說代碼的具體實現(xiàn)
簡介
從FreeRTOS V9.0.0開始,FreeRTOS應(yīng)用程序可以完全靜態(tài)分配,無序包含堆內(nèi)存管理器
動態(tài)內(nèi)存分配及其與 FreeRTOS 的相關(guān)性
從FreeRTOS V9.0.0開始,內(nèi)核對象可以在編譯時靜態(tài)分配,也可以在運(yùn)行時動態(tài)分配,內(nèi)核對象有任務(wù)、隊列、信號量和事件組。為了使FreeRTOS盡可能容易使用,這些內(nèi)核對象不是在編譯時靜態(tài)分配,而是在運(yùn)行時動態(tài)分配。 FreeRTOS會在每次創(chuàng)建內(nèi)核對象時分配RAM,并在每次刪除內(nèi)核對象時釋放RAM。該策略減少了設(shè)計和規(guī)劃工作,簡化了API,并最大限度減少了RAM占用空間。
動態(tài)內(nèi)存分配是一個C編程概念,而不是特定于FreeRTOS或多任務(wù)處理的概念。它與FreeRTOS相關(guān),因為內(nèi)核對象是動態(tài)分配的,通用編譯器提供的動態(tài)內(nèi)存分配方案不總是適合應(yīng)用程序??梢允褂肅標(biāo)準(zhǔn)庫malloc()和free()分配內(nèi)存,但由于一些原因,它們不合適:
- 它們在小型嵌入式系統(tǒng)上并不總是可用。
- 它們的實現(xiàn)可能相對較大,占用寶貴的代碼空間。
- 它們很少是線程安全的。
- 它們不是確定性的; 執(zhí)行函數(shù)所花費的時間會因調(diào)用而異。
- 它們可能會受到碎片化 1 的影響。
- 它們會使鏈接器配置復(fù)雜化。
- 如果允許堆空間增長為其他變量使用的內(nèi)存,它們可能是難以調(diào)試錯誤的根源。
動態(tài)內(nèi)存分配選項
FreeRTOS現(xiàn)在將內(nèi)存分配視為可移植層的一部分(而不是核心代碼庫的一部分)。這是因為不同的嵌入式系統(tǒng)具有不同的動態(tài)內(nèi)存分配和時序要求,從核心代碼庫中刪除動態(tài)內(nèi)存分配使應(yīng)用程序編寫者能夠在適當(dāng)?shù)臅r候提供他們自己的特定實現(xiàn)。
當(dāng)FreeRTOS需要RAM時,它不會調(diào)用malloc(),而是調(diào)用pvPortMalloc(),當(dāng)RAM釋放內(nèi)存時,內(nèi)核不會調(diào)用free(),而是調(diào)用vPortFree()。pvPortMalloc()與標(biāo)準(zhǔn)C庫malloc函數(shù)原型相同,vPortFree()與標(biāo)準(zhǔn)C庫free()函數(shù)原型相同。
pvPortMalloc()和vPortFree()是公共函數(shù),因此也可以從應(yīng)用程序代碼中調(diào)用。
FreeRTOS提供了pvPortMalloc()和vPortFree()的五個示例實現(xiàn),FreeRTOS應(yīng)用程序可以使用示例實現(xiàn)之一,也可以提供自己的實現(xiàn)。這五個實例分別定義在heap_1.c、heap_2.c、heap_3.c、heap_4.c、heap_5.c源文件中,均位于FreeRTOS/Source/portable/MemMang目錄中。
內(nèi)存分配方案
Heap_1
小型專用嵌入式系統(tǒng)通常只在調(diào)度程序啟動前,只創(chuàng)建任務(wù)和其他內(nèi)核對象,在這種情況下,內(nèi)存僅在應(yīng)用程序開始執(zhí)行任何實時功能之前由內(nèi)核動態(tài)分配,并且內(nèi)存在應(yīng)用程序的生命周期內(nèi)保持分配狀態(tài)。 這意味著所選擇的分配方案不必考慮任何更復(fù)雜的內(nèi)存分配問題,例如確定性和碎片,而可以只考慮代碼大小和簡單性等屬性。 也就是說,只分配內(nèi)存,其他像把兩個相鄰的空閑塊整合到一塊,這個是不管的。
Heap_1.c實現(xiàn)了一個非常基本的pvPortMalloc()版本,并沒有實現(xiàn)vPortFree(),所以從不刪除任務(wù)或其他內(nèi)核對象的應(yīng)用程序可以使用heap_1。當(dāng)調(diào)用pvPortMalloc()時,heap_1分配方案將一個堆細(xì)分為更小的塊,堆的總大小由FreeRTOSConfig.h中的宏configTOTAL_HEAP_SIZE設(shè)置,但是為字節(jié)。
每個創(chuàng)建的任務(wù)都要兩個內(nèi)存塊,一個是任務(wù)控制塊(TCB),另一個堆棧,這兩個都從堆分配。下圖演示來了heap_1如何在創(chuàng)建任務(wù)時細(xì)分堆
- A 顯示創(chuàng)建任何任務(wù)之前的堆——整個堆都是空閑的。
- B 顯示創(chuàng)建一項任務(wù)后的堆。
- C 顯示創(chuàng)建三個任務(wù)后的堆。
heap_1適用于那些一旦創(chuàng)建好任務(wù)、信號量、隊列和事件組就再也不會刪除的應(yīng)用。一些禁止使用動態(tài)內(nèi)存分配的商業(yè)關(guān)鍵和安全關(guān)鍵系統(tǒng)也有可能使用 heap_1。由于與非確定性、內(nèi)存碎片和分配失敗相關(guān)的不確定性,關(guān)鍵系統(tǒng)通常禁止動態(tài)內(nèi)存分配——但 Heap_1 始終是確定性的,不能對內(nèi)存進(jìn)行碎片化。heap_1代碼實現(xiàn)和內(nèi)存分配過程都很簡單,內(nèi)存是從一個靜態(tài)數(shù)組(堆)分配到的,也就是適合那些不需要動態(tài)內(nèi)存分配的應(yīng)用
heap_2
為了向后兼容,FreeRTOS保留了Heap_21,但不建議使用它,使用heap_4,因為heap_4提供了增強(qiáng)的功能。
heap_2.c的大小也通過configTOTAL_HEAP_SIZE確定,它使用最佳擬合算法來分配內(nèi)存,并且與heap_1不同,它允許釋放內(nèi)存。同樣數(shù)組(堆)是靜態(tài)聲明的,因此會消耗大量的RAM,最佳擬合算法確保PVPortMalloc()使用大小與請求的字節(jié)數(shù)最接近的空閑內(nèi)存塊,比如,考慮以下場景:堆包含三個空閑內(nèi)存塊,分為為5字節(jié)、25字節(jié)和100字節(jié),調(diào)用pvPortMalloc來請求20字節(jié)的RAM。可以容納請求的字節(jié)數(shù)最小RAM空閑塊是25字節(jié),因此pvPortMalloc()將25字節(jié)塊分為一個20字節(jié)塊和一個5字節(jié)塊,然后返回指向20字節(jié)的塊的指針,新的5字節(jié)塊依然可以被pvPortMalloc調(diào)用。
與heap_4不同,heap_2不會將相鄰的空閑塊合并為一個更大的塊,因此容易發(fā)生碎片。 Heap_2適用于重復(fù)創(chuàng)建和刪除任務(wù)的應(yīng)用程序,前提是分配給創(chuàng)建的任務(wù)和堆棧大小不變。
下圖演示了在創(chuàng)建、刪除和再次創(chuàng)建任務(wù)時最佳擬合算法的工作原理:
Heap_2 不是確定性的,但比 malloc() 和 free() 的大多數(shù)標(biāo)準(zhǔn)庫實現(xiàn)要快。
Heap_3
Heap_3使用標(biāo)準(zhǔn)庫malloc()和free()函數(shù),因此堆的大小由鏈接器配置定義,configTOTAL_HEAP_SIZE設(shè)置對其沒有影響,Heap_3通過暫時掛起FreeRTOS調(diào)度程序使malloc()和free()線程安全。
Heap_4
與heap_1和heap_2一樣,heap_4的工作原理是將數(shù)組細(xì)分為更小的塊,數(shù)組是靜態(tài)分配的,并有configTOTAL_HEAP_SIZE確定尺寸,heap_4使用first fit算法來分配內(nèi)存,與heap_2冉,heap_4將合并相鄰的空閑內(nèi)存塊,然后組合成一個更大的塊,從而最大限度的降低內(nèi)存碎片的風(fēng)險。
the first fit算法確保pvPortMalloc()使用第一個能夠容納請求字節(jié)數(shù)的空閑塊,例如,考慮以下場景:
堆包含三個空閑內(nèi)存塊,按照它們在數(shù)組中出現(xiàn)的順序,分別為5字節(jié)、200字節(jié)和100字節(jié),調(diào)用pvPortMalloc()來請求20字節(jié)的RAM,第一個適合請求的字節(jié)數(shù)的空閑塊是200字節(jié),因此pvPortMalloc在返回指針之前將200字節(jié)拆分為一個20字節(jié)的塊和一個180字節(jié)的塊,返回的指針指向20字節(jié)的塊,新的180字節(jié)塊仍可以被pvPortMalloc調(diào)用。
Heap_4將合并相鄰的空閑塊組合成一個更大的塊,最大限度的降低碎片的風(fēng)險,并使其適用于重復(fù)分配和釋放不同大小的RAM塊的應(yīng)用程序。
下圖演示了具有內(nèi)存合并的 heap_4 首次擬合算法的工作原理,如內(nèi)存被分配和釋放:
刪除任務(wù)時釋放的內(nèi)存現(xiàn)在已拆分為三個單獨的塊;第一個塊保存隊列,第二個塊保存用戶分配的內(nèi)存,第三個塊保持空閑。
Heap_4 不是確定性的,但比 malloc() 和 free() 的大多數(shù)標(biāo)準(zhǔn)庫實現(xiàn)要快。
設(shè)置heap_4的起始地址
有時,應(yīng)用程序編寫者需要將 heap_4 使用的數(shù)組放置在特定的內(nèi)存地址。 例如,FreeRTOS 任務(wù)使用的堆棧是從堆分配的,因此可能有必要確保堆位于快速內(nèi)部內(nèi)存中,而不是位于慢速外部內(nèi)存中。
默認(rèn)情況下,heap_4 使用的數(shù)組在 heap_4.c 源文件中聲明,其起始地址由鏈接器自動設(shè)置。 但是,如果 FreeRTOSConfig.h 中的 configAPPLICATION_ALLOCATED_HEAP 設(shè)置為 1,則該數(shù)組必須改為由使用 FreeRTOS 的應(yīng)用程序聲明。 如果數(shù)組被聲明為應(yīng)用程序的一部分,那么應(yīng)用程序的編寫者可以設(shè)置它的起始地址。
如果 configAPPLICATION_ALLOCATED_HEAP 在 FreeRTOSConfig.h 中設(shè)置為 1,則必須在應(yīng)用程序的源文件之一中聲明一個名為 ucHeap 并由 configTOTAL_HEAP_SIZE 設(shè)置大小的 uint8_t 數(shù)組。
Heap_5
heap_5用于分配和釋放內(nèi)存的算法和heap_4相同,不同的是heap_5不限于從單個靜態(tài)聲明的數(shù)組分配內(nèi)存,heap_5可以從多個獨立的內(nèi)存空間分配內(nèi)存,當(dāng)運(yùn)行 FreeRTOS 的系統(tǒng)提供的 RAM 沒有在系統(tǒng)內(nèi)存映射中顯示為單個連續(xù)(沒有空間)塊時,Heap_5 很有用。
在撰寫本文時,heap_5 是唯一提供的內(nèi)存分配方案,必須在調(diào)用 pvPortMalloc() 之前顯式初始化。 Heap_5 使用 vPortDefineHeapRegions() API 函數(shù)初始化。 使用 heap_5 時,必須在創(chuàng)建任何內(nèi)核對象(任務(wù)、隊列、信號量等)之前調(diào)用 vPortDefineHeapRegions()。
vPortDefineHeapRegions()
vPortDefineHeapRegions用于指定每個單獨的內(nèi)存區(qū)域的起始地址和大小,這些區(qū)域構(gòu)成heap_5使用的總內(nèi)存。
void vPortDefineHeapRegions( const HeapRegion_t * const pxHeapRegions );每個單獨的內(nèi)存區(qū)域由HeapRegion_t類型的結(jié)構(gòu)體描述**,所有可用內(nèi)存區(qū)域的描述**作為 HeapRegion_t 結(jié)構(gòu)數(shù)組傳遞到 vPortDefineHeapRegions()。
typedef struct HeapRegion { /* The start address of a block of memory that will be part of the heap.*/ uint8_t *pucStartAddress; /* The size of the block of memory in bytes. */ size_t xSizeInBytes; } HeapRegion_t;pxHeapRegions 指向 HeapRegion_t 結(jié)構(gòu)數(shù)組開頭的指針。 數(shù)組中的每個結(jié)構(gòu)都描述了內(nèi)存的起始地址和長度,數(shù)組中的 HeapRegion_t 結(jié)構(gòu)體必須按起始地址排序; 描述起始地址最低的內(nèi)存區(qū)域的 HeapRegion_t 結(jié)構(gòu)必須是數(shù)組中的第一個結(jié)構(gòu),描述起始地址最高的內(nèi)存區(qū)域HeapRegion_t 結(jié)構(gòu)必須是數(shù)組中的最后一個結(jié)構(gòu)。數(shù)組的末尾由 HeapRegion_t 結(jié)構(gòu)標(biāo)記,該結(jié)構(gòu)的 pucStartAddress 成員設(shè)置為 NULL。
例如,考慮下圖中所示的假設(shè)內(nèi)存映射,其中包含三個獨立的RAM塊:RAM1、RAM2和RAM3,假設(shè)可執(zhí)行代碼放置在只讀寄存器中,沒有顯示。
下面的代碼描述了整個RAM的三個塊
雖然正確地描述了 RAM,但它沒有展示一個可用的示例,因為它將所有 RAM 分配給堆,沒有空閑 RAM 可供其他變量使用。
構(gòu)建項目時,構(gòu)建過程的鏈接階段會為每個變量分配一個 RAM 地址。 可供鏈接器使用的 RAM 通常由鏈接器配置文件(例如鏈接描述文件)描述。假設(shè)鏈接描述文件包含關(guān)于 RAM1 的信息,但不包含關(guān)于 RAM2 或 RAM3 的信息。 因此,鏈接器將變量放置在 RAM1 中,只留下地址 0x0001nnnn 以上的 RAM1 部分可供 heap_5 使用。 0x0001nnnn 的實際值將取決于所鏈接的應(yīng)用程序中包含的所有變量的組合大小。 鏈接器讓所有 RAM2 和所有 RAM3 未使用,留下整個 RAM2 和整個 RAM3 可供 heap_5 使用。
如果使用上述 中所示的代碼,分配給地址 0x0001nnnn 下的 heap_5 的 RAM 將與用于保存變量的 RAM 重疊。為了避免這種情況,xHeapRegions[] 數(shù)組中的第一個 HeapRegion_t 結(jié)構(gòu)可以使用 0x0001nnnn 的起始地址,而不是 0x00010000 的起始地址。**但是,這不是推薦的解決方案,**因為:
下面 展示了一個更方便和可維護(hù)的示例。它聲明了一個名為 ucHeap 的數(shù)組。 ucHeap 是一個普通變量,因此它成為鏈接器分配給 RAM1 的數(shù)據(jù)的一部分。 xHeapRegions 數(shù)組中的第一個 HeapRegion_t 結(jié)構(gòu)描述了 ucHeap 的起始地址和大小,因此 ucHeap 成為 heap_5 管理的內(nèi)存的一部分。 ucHeap 的大小可以增加,直到鏈接器使用的 RAM 耗盡所有 RAM1
堆相關(guān)的函數(shù)
xPortGetFreeHeapSize
xPortGetFreeHeapSize函數(shù)返回堆中的空閑字節(jié)數(shù),它可用于優(yōu)化堆大小。 例如,如果 xPortGetFreeHeapSize() 在所有內(nèi)核對象創(chuàng)建后返回 2000,那么 configTOTAL_HEAP_SIZE 的值可以減少 2000。
size_t xPortGetFreeHeapSize( void );xPortGetMinimumEverFreeHeapSize
xPortGetMinimumEverFreeHeapSize返回自FreeRTOS應(yīng)用程序開始執(zhí)行以來,堆中存在最小未分配字節(jié)數(shù)。xPortGetMinimumEverFreeHeapSize返回值表明應(yīng)用程序接近耗盡堆空間的程序, 例如,如果 xPortGetMinimumEverFreeHeapSize() 返回 200,那么在應(yīng)用程序開始執(zhí)行后的某個時間,它會在 200 字節(jié)內(nèi)耗盡堆空間。
xPortGetMinimumEverFreeHeapSize() 僅在使用 heap_4 或 heap_5 時可用。
Malloc調(diào)用失敗的Hook函數(shù)
pvPortMalloc() 可以直接從應(yīng)用程序代碼中調(diào)用。每次創(chuàng)建內(nèi)核對象時,它也會在 FreeRTOS 源文件中調(diào)用。內(nèi)核對象的例子包括任務(wù)、隊列、信號量和事件組。
像標(biāo)準(zhǔn)庫 malloc() 函數(shù)一樣,如果 pvPortMalloc() 因為請求大小的塊不存在而無法返回 RAM 塊,那么它將返回 NULL,則不會創(chuàng)建內(nèi)核對象。
如果對 pvPortMalloc() 的調(diào)用返回 NULL,則所有示例堆分配方案都可以配置一個調(diào)用掛鉤(或回調(diào))函數(shù)。如果在 FreeRTOSConfig.h 中將 configUSE_MALLOC_FAILED_HOOK 設(shè)置為 1,那么應(yīng)用程序必須提供一個 malloc 失敗的鉤子函數(shù)。該函數(shù)可以以任何適合應(yīng)用程序的方式實現(xiàn),該函數(shù)如下:
總結(jié)
以上是生活随笔為你收集整理的FreeRTOS---堆内存管理(一)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: “岂但避霜雪”下一句是什么
- 下一篇: 铃木天语多少钱啊?