日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

FreeRTOS高级篇7---FreeRTOS内存管理分析

發布時間:2025/4/5 编程问答 32 豆豆
生活随笔 收集整理的這篇文章主要介紹了 FreeRTOS高级篇7---FreeRTOS内存管理分析 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
內存管理對應用程序和操作系統來說都非常重要。現在很多的程序漏洞和運行崩潰都和內存分配使用錯誤有關。
? ? ? ? FreeRTOS操作系統將內核與內存管理分開實現,操作系統內核僅規定了必要的內存管理函數原型,而不關心這些內存管理函數是如何實現的。這樣做大有好處,可以增加系統的靈活性:不同的應用場合可以使用不同的內存分配實現,選擇對自己更有利的內存管理策略。比如對于安全型的嵌入式系統,通常不允許動態內存分配,那么可以采用非常簡單的內存管理策略,一經申請的內存,甚至不允許被釋放。在滿足設計要求的前提下,系統越簡單越容易做的更安全。再比如一些復雜應用,要求動態的申請、釋放內存操作,那么也可以設計出相對復雜的內存管理策略,允許動態分配和動態釋放。
FreeRTOS內核規定的幾個內存管理函數原型為:
  • void *pvPortMalloc( size_t xSize ) :內存申請函數
  • void vPortFree( void *pv ) :內存釋放函數
  • void vPortInitialiseBlocks( void ) :初始化內存堆函數
  • size_t xPortGetFreeHeapSize( void ) :獲取當前未分配的內存堆大小
  • size_t xPortGetMinimumEverFreeHeapSize( void ):獲取未分配的內存堆歷史最小值
  • FreeRTOS提供了5種內存管理實現,有簡單也有復雜的,可以應用于絕大多數場合。它們位于下載包目錄...\FreeRTOS\Source\portable\MemMang中,文件名分別為:heap_1.c、heap_2.c、heap_3.c、heap_4.c、heap_5.c。我在《FreeRTOS系列第8篇---FreeRTOS內存管理》這篇文章中介紹了這5種內存管理的特性以及各自應用的場合,今天我們要分析它們的實現方法。
    FreeRTOS提供的內存管理都是從內存堆中分配內存的。默認情況下,FreeRTOS內核創建任務、隊列、信號量、事件組、軟件定時器都是借助內存管理函數從內存堆中分配內存。最新的FreeRTOS版本(V9.0.0及其以上版本)可以完全使用靜態內存分配方法,也就是不使用任何內存堆。
    對于heap_1.c、heap_2.c和heap_4.c這三種內存管理策略,內存堆實際上是一個很大的數組,定義為:
    static uint8_t ucHeap[ configTOTAL_HEAP_SIZE ];
    其中宏configTOTAL_HEAP_SIZE用來定義內存堆的大小,這個宏在FreeRTOSConfig.h中設置。
    對于heap_3.c,這種策略只是簡單的包裝了標準庫中的malloc()和free()函數,包裝后的malloc()和free()函數具備線程保護。因此,內存堆需要通過編譯器或者啟動文件設置堆空間。
    heap_5.c比較有趣,它允許程序設置多個非連續內存堆,比如需要快速訪問的內存堆設置在片內RAM,稍微慢速訪問的內存堆設置在外部RAM。每個內存堆的起始地址和大小由應用程序設計者定義。

    1. heap_1.c

    ? ? ? ? 這是5個內存管理策略中最簡單的一個,我們稱為第一個內存管理策略,它簡單到只能申請內存。是的,跟你想的一樣,一旦申請成功后,這塊內存再也不能被釋放。對于大多數嵌入式系統,特別是對安全要求高的嵌入式系統,這種內存管理策略很有用,因為對系統軟件來說,邏輯越簡單越容易兼顧安全。實際上,大多數的嵌入式系統并不需要動態刪除任務、信號量、隊列等,而是在初始化的時候一次性創建好,便一直使用,永遠不用刪除。所以這個內存管理策略實現簡潔、安全可靠,使用的非常廣泛。我對這個對內存管理策略也情有獨鐘。
    ? ? ? ? 我們可以將第一種內存管理看作是切面包:初始化的內存就像一根完整的長棍面包,每次申請內存,就從一端切下適當長度的面包返還給申請者,直到面包被分配完畢,就這么簡單。
    這個內存管理策略使用兩個局部靜態變量來跟蹤內存分配,變量定義為:
    [objc]?view plaincopy print?
  • static?size_t?xNextFreeByte?=?(?size_t?)?0;??
  • static?uint8_t?*pucAlignedHeap?=?NULL;??
  • 其中,變量xNextFreeByte記錄已經分配的內存大小,用來定位下一個空閑的內存堆位置。因為內存堆實際上是一個大數組,我們只需要知道已分配內存的大小,就可以用它作為偏移量找到未分配內存的起始地址。變量xNextFreeByte被初始化為0,然后每次申請內存成功后,都會增加申請內存的字節數目。
    變量pucAlignedHeap指向對齊后的內存堆起始位置。為什么要對齊?這是因為大多數硬件訪問內存對齊的數據速度會更快。為了提高性能,FreeRTOS會進行對齊操作,不同的硬件架構對齊操作也不盡相同,對于Cortex-M3架構,進行8字節對齊。
    我們來看一下第一種內存管理策略對外提供的API函數。

    1.1內存申請:pvPortMalloc()

    函數源碼為:
    [objc]?view plaincopy print?
  • voidvoid?*pvPortMalloc(?size_t?xWantedSize?)??
  • {??
  • voidvoid?*pvReturn?=?NULL;??
  • static?uint8_t?*pucAlignedHeap?=?NULL;??
  • ??
  • ??
  • ????/*?確保申請的字節數是對齊字節數的倍數?*/??
  • ????#if(?portBYTE_ALIGNMENT?!=?1?)??
  • ????{??
  • ????????if(?xWantedSize?&?portBYTE_ALIGNMENT_MASK?)??
  • ????????{??
  • ????????????xWantedSize?+=?(?portBYTE_ALIGNMENT?-?(?xWantedSize?&?portBYTE_ALIGNMENT_MASK?)?);??
  • ????????}??
  • ????}??
  • ????#endif??
  • ??
  • ??
  • ????vTaskSuspendAll();??
  • ????{??
  • ????????if(?pucAlignedHeap?==?NULL?)??
  • ????????{??
  • ????????????/*?第一次使用,確保內存堆起始位置正確對齊?*/??
  • ????????????pucAlignedHeap?=?(?uint8_t?*?)?(?(?(?portPOINTER_SIZE_TYPE?)?&ucHeap[?portBYTE_ALIGNMENT?]?)?&?(?~(?(?portPOINTER_SIZE_TYPE?)?portBYTE_ALIGNMENT_MASK?)?)?);??
  • ????????}??
  • ??
  • ??
  • ????????/*?邊界檢查,變量xNextFreeByte是局部靜態變量,初始值為0?*/??
  • ????????if(?(?(?xNextFreeByte?+?xWantedSize?)?<?configADJUSTED_HEAP_SIZE?)?&&??
  • ????????????(?(?xNextFreeByte?+?xWantedSize?)?>?xNextFreeByte?)?)??
  • ????????{??
  • ????????????/*?返回申請的內存起始地址并更新索引?*/??
  • ????????????pvReturn?=?pucAlignedHeap?+?xNextFreeByte;??
  • ????????????xNextFreeByte?+=?xWantedSize;??
  • ????????}??
  • ????}??
  • ????(?void?)?xTaskResumeAll();??
  • ??
  • ??
  • ????#if(?configUSE_MALLOC_FAILED_HOOK?==?1?)??
  • ????{??
  • ????????if(?pvReturn?==?NULL?)??
  • ????????{??
  • ????????????extern?void?vApplicationMallocFailedHook(?void?);??
  • ????????????vApplicationMallocFailedHook();??
  • ????????}??
  • ????}??
  • ????#endif??
  • ??
  • ??
  • ????return?pvReturn;??
  • }??
  • ? ? ? ? 函數一開始會將申請的內存數量調整到對齊字節數的整數倍,所以實際分配的內存空間可能比申請內存大。比如對于8字節對齊的系統,申請11字節內存,經過對齊后,實際分配的內存是16字節(8的整數倍)。
    接下來會掛起所有任務,因為內存申請是不可重入的(使用了靜態變量)。?
    如果是第一次執行這個函數,需要將變量pucAlignedHeap指向內存堆區域第一個地址對齊處。我們上面說內存堆其實是一個大數組,編譯器為這個數組分配的起始地址是隨機的,可能不符合我們的對齊需要,這時候要進行調整。比如內存堆數組ucHeap從RAM地址0x10002003處開始,系統按照8字節對齊,則對齊后的內存堆如圖1-1所示:
    圖1-1:內存堆大小與地址對齊示意圖 之后進行邊界檢查,查看剩余的內存堆是否夠分配,檢查xNextFreeByte + xWantedSize是否溢出。如果檢查通過,則為申請者返回有效的內存指針并更新已分配內存數量計數器xNextFreeByte(從指針pucAlignedHeap開始,偏移量為xNextFreeByte處的內存區域為未分配的內存堆起始位置)。比如我們首次調用內存分配函數pvPortMalloc(20),申請20字節內存。根據對齊原則,我們會實際申請到24字節內存,申請成功后,內存堆示意圖如圖1-2所示。
    ? 圖1-2:第一次分配內存后的內存堆空間示意圖 內存分配完成后,不管有沒有分配成功都恢復之前掛起的調度器。
    如果內存分配不成功,這里最可能是內存堆空間不夠用了,會調用一個鉤子函數vApplicationMallocFailedHook()。這個鉤子函數由應用程序提供,通常我們可以打印內存分配設備信息或者點亮也故障指示燈。

    1.2獲取當前未分配的內存堆大小:xPortGetFreeHeapSize()

    函數用于返回未分配的內存堆大小。這個函數也很有用,通常用于檢查我們設置的內存堆是否合理,通過這個函數我們可以估計出最壞情況下需要多大的內存堆,以便合理的節省RAM。
    對于第一個內存管理策略,這個函數實現十分簡單,源碼如下:
    [objc]?view plaincopy print?
  • size_t?xPortGetFreeHeapSize(?void?)??
  • {??
  • ????return?(?configADJUSTED_HEAP_SIZE?-?xNextFreeByte?);??
  • }??
  • 從圖1-1和圖1-2我們知道,宏configADJUSTED_HEAP_SIZE表示內存堆有效的大小,這個值減去已經分配出去的內存大小,正是我們需要的未分配的內存堆大小。

    1.3其它函數

    第一個內存管理策略中還有兩個函數:vPortFree()和vPortInitialiseBlocks()。但實際上第一個函數什么也不做;第二個函數僅僅將靜態局部變量xNextFreeByte設置為0。

    2. heap_2.c

    ? ? ? ? 第二種內存管理策略要比第一種內存管理策略復雜,它使用一個最佳匹配算法,允許釋放之前已分配的內存塊,但是它不會把相鄰的空閑塊合成一個更大的塊(換句話說,這會造成內存碎片)。
    ? ? ? ? 這個內存管理策略用于重復的分配和刪除具有相同堆棧空間的任務、隊列、信號量、互斥量等等,并且不考慮內存碎片的應用程序,不適用于分配和釋放隨機字節堆棧空間的應用程序!
    ? ? ? ? 與第一種內存管理策略一樣,內存堆仍然是一個大數組,定義為:
    [objc]?view plaincopy print?
  • static?uint8_t?ucHeap[?configTOTAL_HEAP_SIZE?];??
  • 局部靜態變量pucAlignedHeap指向對齊后的內存堆起始位置。地址對齊的原因在第一種內存管理策略中已經說明。假如內存堆數組ucHeap從RAM地址0x10002003處開始,系統按照8字節對齊,則對齊后的內存堆與第一個內存管理策略一樣,如圖2-1所示:
    ? 圖2-1:內存堆示大小與地址對齊示意圖

    2.1內存申請:pvPortMalloc()

    與第一種內存管理策略不同,第二種內存管理策略使用一個鏈表結構來跟蹤記錄空閑內存塊,將空閑塊組成一個鏈表。結構體定義為:
    [objc]?view plaincopy print?
  • typedef?struct?A_BLOCK_LINK??
  • {??
  • ????struct?A_BLOCK_LINK?*pxNextFreeBlock;???/*指向列表中下一個空閑塊*/??
  • ????size_t?xBlockSize;??????????????????????/*當前空閑塊的大小,包括鏈表結構大小*/??
  • }?BlockLink_t;??
  • 兩個BlockLink_t類型的局部靜態變量xStart和xEnd用來標識空閑內存塊的起始和結束。剛開始時,整個內存堆有效空間就是一個空閑塊,如圖2-2所示。因為要包含的信息越來越多,我們必須舍棄一些信息,舍棄的信息可以在上一幅圖中找到。
    ? 圖2-2:內存堆初始化示意圖 ? ? ? ? 圖2-2中的pvReturn是我自己增加的,用于接下來分析內存申請操作,堆棧初始化并沒有這個變量,也沒有對其操作的代碼。從圖2-2中可以看出,整個有效空間組成唯一一個空閑塊,在空閑塊的起始位置放置了一個鏈表結構,用于存儲這個空閑塊的大小和下一個空閑塊的地址。由于目前只有一個空閑塊,所以空閑塊的pxNextFreeBlock指向鏈表xEnd,而鏈表xStart結構的pxNextFreeBlock指向空閑塊。這樣,xStart、空閑塊和xEnd組成一個單鏈表,xStart表示鏈表頭,xEnd表示鏈表尾。隨著內存申請和釋放,空閑塊可能會越來越多,但它們仍是以xStart鏈表開頭以xEnd鏈表結尾,根據空閑塊的大小排序,小的在前,大的在后,我們在內存釋放一節中會給出示意圖。
    ? ? ? ?當申請N字節內存時,實際上不僅需要分配N字節內存,還要分配一個BlockLink_t類型結構體空間,用于描述這個內存塊,結構體空間位于空閑內存塊的最開始處。當然,和第一種內存管理策略一樣,申請的內存大小和BlockLink_t類型結構體大小都要向上擴大到對齊字節數的整數倍。
    ? ? ? ? 我們看一下內存申請過程:首先計算實際要分配的內存大小,判斷申請的內存是否合法。如果合法則從鏈表頭xStart開始查找,如果某個空閑塊的xBlockSize字段大小能容得下要申請的內存,則從這塊內存取出合適的部分返回給申請者,剩下的內存塊組成一個新的空閑塊,按照空閑塊的大小順序插入到空閑塊鏈表中,小塊在前大塊在后。注意,返回的內存中不包括鏈表結構,而是緊鄰鏈表結構(經過對齊)后面的位置。舉個例子,如圖2-2所示的內存堆,當調用申請內存函數,如果內存堆空間足夠大,就將pvReturn指向的地址返回給申請者,而不是靜態變量pucAlignedHeap指向的內存堆起始位置!
    ? ? ? ? 當多次調用內存申請函數后(沒有調用內存釋放函數),內存堆結構如圖2-3所示。注意圖中的pvReturn仍是我自己增加上去的,pvReturn指向的位置返回給申請者。后面我們講內存釋放時,就是根據這個地址完成內存釋放工作的。
    ? 圖2-3:經過兩次內存分配后的內存堆示意圖 有了上面的這些基礎知識,再看內存申請函數源碼就比較簡單了,我把需要注意的要點以注釋的方式放在源碼中,不再單獨對這個函數做講解,值得注意的是函數中使用的一個靜態局部變量xFreeBytesRemaining,它用來記錄未分配的內存堆大小。這個變量將提供給函數xPortGetFreeHeapSize()使用,以方便用戶估算內存堆使用情況。
    [objc]?view plaincopy print?
  • voidvoid?*pvPortMalloc(?size_t?xWantedSize?)??
  • {??
  • BlockLink_t?*pxBlock,?*pxPreviousBlock,?*pxNewBlockLink;??
  • static?BaseType_t?xHeapHasBeenInitialised?=?pdFALSE;??
  • voidvoid?*pvReturn?=?NULL;??
  • ??
  • ??
  • ????/*?掛起調度器?*/??
  • ????vTaskSuspendAll();??
  • ????{??
  • ????????/*?如果是第一次調用內存分配函數,這里先初始化內存堆,如圖2-2所示?*/??
  • ????????if(?xHeapHasBeenInitialised?==?pdFALSE?)??
  • ????????{??
  • ????????????prvHeapInit();??
  • ????????????xHeapHasBeenInitialised?=?pdTRUE;??
  • ????????}??
  • ??
  • ??
  • ????????/*?調整要分配的內存值,需要增加上鏈表結構體空間,heapSTRUCT_SIZE表示經過對齊擴展后的結構體大小?*/??
  • ????????if(?xWantedSize?>?0?)??
  • ????????{??
  • ????????????xWantedSize?+=?heapSTRUCT_SIZE;??
  • ??
  • ??
  • ????????????/*?調整實際分配的內存大小,向上擴大到對齊字節數的整數倍?*/??
  • ????????????if(?(?xWantedSize?&?portBYTE_ALIGNMENT_MASK?)?!=?0?)??
  • ????????????{??
  • ????????????????xWantedSize?+=?(?portBYTE_ALIGNMENT?-?(?xWantedSize?&?portBYTE_ALIGNMENT_MASK?)?);??
  • ????????????}??
  • ????????}??
  • ??????????
  • ????????if(?(?xWantedSize?>?0?)?&&?(?xWantedSize?<?configADJUSTED_HEAP_SIZE?)?)??
  • ????????{??
  • ????????????/*?空閑內存塊是按照塊的大小排序的,從鏈表頭xStart開始,小的在前大的在后,以鏈表尾xEnd結束?*/??
  • ????????????pxPreviousBlock?=?&xStart;??
  • ????????????pxBlock?=?xStart.pxNextFreeBlock;??
  • ????????????/*?搜索最合適的空閑塊?*/??
  • ????????????while(?(?pxBlock->xBlockSize?<?xWantedSize?)?&&?(?pxBlock->pxNextFreeBlock?!=?NULL?)?)??
  • ????????????{??
  • ????????????????pxPreviousBlock?=?pxBlock;??
  • ????????????????pxBlock?=?pxBlock->pxNextFreeBlock;??
  • ????????????}??
  • ??
  • ??
  • ????????????/*?如果搜索到鏈表尾xEnd,說明沒有找到合適的空閑內存塊,否則進行下一步處理?*/??
  • ????????????if(?pxBlock?!=?&xEnd?)??
  • ????????????{??
  • ????????????????/*?返回內存空間,注意是跳過了結構體BlockLink_t空間.?*/??
  • ????????????????pvReturn?=?(?voidvoid?*?)?(?(?(?uint8_t?*?)?pxPreviousBlock->pxNextFreeBlock?)?+?heapSTRUCT_SIZE?);??
  • ??
  • ??
  • ????????????????/*?這個塊就要返回給用戶,因此它必須從空閑塊中去除.?*/??
  • ????????????????pxPreviousBlock->pxNextFreeBlock?=?pxBlock->pxNextFreeBlock;??
  • ??
  • ??
  • ????????????????/*?如果這個塊剩余的空間足夠多,則將它分成兩個,第一個返回給用戶,第二個作為新的空閑塊插入到空閑塊列表中去*/??
  • ????????????????if(?(?pxBlock->xBlockSize?-?xWantedSize?)?>?heapMINIMUM_BLOCK_SIZE?)??
  • ????????????????{??
  • ????????????????????/*?去除分配出去的內存,在剩余內存塊的起始位置放置一個鏈表結構并初始化鏈表成員?*/??
  • ????????????????????pxNewBlockLink?=?(?voidvoid?*?)?(?(?(?uint8_t?*?)?pxBlock?)?+?xWantedSize?);??
  • ??
  • ??
  • ????????????????????pxNewBlockLink->xBlockSize?=?pxBlock->xBlockSize?-?xWantedSize;??
  • ????????????????????pxBlock->xBlockSize?=?xWantedSize;??
  • ??
  • ??
  • ????????????????????/*?將剩余的空閑塊插入到空閑塊列表中,按照空閑塊的大小順序,小的在前大的在后?*/??
  • ????????????????????prvInsertBlockIntoFreeList(?(?pxNewBlockLink?)?);??
  • ????????????????}??
  • ????????????????/*?計算未分配的內存堆大小,注意這里并不能包含內存碎片信息?*/??
  • ????????????????xFreeBytesRemaining?-=?pxBlock->xBlockSize;??
  • ????????????}??
  • ????????}??
  • ??
  • ??
  • ????????traceMALLOC(?pvReturn,?xWantedSize?);??
  • ????}??
  • ????(?void?)?xTaskResumeAll();??
  • ??
  • ??
  • ????#if(?configUSE_MALLOC_FAILED_HOOK?==?1?)??
  • ????{???/*?如果內存分配失敗,調用鉤子函數?*/??
  • ????????if(?pvReturn?==?NULL?)??
  • ????????{??
  • ????????????extern?void?vApplicationMallocFailedHook(?void?);??
  • ????????????vApplicationMallocFailedHook();??
  • ????????}??
  • ????}??
  • ????#endif??
  • ??
  • ??
  • ????return?pvReturn;??
  • }??
  • 2.2內存釋放:vPortFree()

    因為不需要合并相鄰的空閑塊,第二種內存管理策略的內存釋放也非常簡單:根據傳入的參數找到鏈表結構,然后將這個內存塊插入到空閑塊列表,更新未分配的內存堆計數器大小,結束。因為簡單,我們直接看源碼。
    [objc]?view plaincopy print?
  • void?vPortFree(?voidvoid?*pv?)??
  • {??
  • uint8_t?*puc?=?(?uint8_t?*?)?pv;??
  • BlockLink_t?*pxLink;??
  • ??
  • ??
  • ????if(?pv?!=?NULL?)??
  • ????{??
  • ????????/*?根據傳入的參數找到鏈表結構?*/??
  • ????????puc?-=?heapSTRUCT_SIZE;??
  • ??
  • ??
  • ????????/*?預防某些編譯器警告?*/??
  • ????????pxLink?=?(?voidvoid?*?)?puc;??
  • ??
  • ??
  • ????????vTaskSuspendAll();??
  • ????????{??
  • ????????????/*?將這個塊添加到空閑塊列表?*/??
  • ????????????prvInsertBlockIntoFreeList(?(?(?BlockLink_t?*?)?pxLink?)?);??
  • ????????????/*?更新未分配的內存堆大小?*/??
  • ????????????xFreeBytesRemaining?+=?pxLink->xBlockSize;??
  • ??????????????
  • ????????????traceFREE(?pv,?pxLink->xBlockSize?);??
  • ????????}??
  • ????????(?void?)?xTaskResumeAll();??
  • ????}??
  • }??
  • 我們舉一個例子,將圖2-3 pvReturn指向的內存塊釋放掉,假設(configADJUSTED_HEAP_SIZE-40)遠大于要釋放的內存塊大小,釋放后的內存堆如圖2-4所示:
    圖2-4:釋放內存后,內存堆示意圖 從圖2-4我們可以看出第二種內存管理策略的兩個特點:第一,空閑塊是按照大小排序的;第二,相鄰的空閑塊不會組合成一個大塊。
    我們再接著引申討論一下這種內存管理策略的優缺點。通過對內存申請和釋放函數源碼分析,我們可以看出它的一個優點是速度足夠快,因為它的實現非常簡單;第二個優點是可以動態釋放內存。但是它的缺點也非常明顯:由于在釋放內存時不會將相鄰的內存塊合并,所以這可能造成內存碎片。這就對其應用的場合要求極其苛刻:第一,每次創建或釋放的任務、信號量、隊列等必須大小相同,如果分配或釋放的內存是隨機的,絕對不可以用這種內存管理策略;第二,如果申請和釋放的順序不可預料,也很危險。舉個例子,對于一個已經初始化的10KB內存堆,先申請48字節內存,然后釋放;再接著申請32字節內存,那么一個本來48字節的大塊就會被分為32字節和16字節的小塊,如果這種情況經常發生,就會導致每個空閑塊都可能很小,最終在申請一個大塊時就會因為沒有合適的空閑塊而申請失敗(并不是因為總的空閑內存不足)!

    2.3獲取未分配的內存堆大小:xPortGetFreeHeapSize()

    函數用于返回未分配的內存堆大小。這個函數也很有用,通常用于檢查我們設置的內存堆是否合理,通過這個函數我們可以估計出最壞情況下需要多大的內存堆,以便進行合理的節省RAM。需要注意的是,這個函數返回值并不能函數源碼為:
    [objc]?view plaincopy print?
  • size_t?xPortGetFreeHeapSize(?void?)??
  • {??
  • ????return?xFreeBytesRemaining;??
  • }??
  • 局部靜態變量xFreeBytesRemaining在內存申請和內存釋放函數中多次提到,它用來動態記錄未分配的內存堆大小。

    3.heap_3.c

    第三種內存管理策略簡單的封裝了標準庫中的malloc()和free()函數,采用的封裝方式是操作內存前掛起調度器、完成后再恢復調度器。封裝后的malloc()和free()函數具備線程保護。
    第一種和第二種內存管理策略都是通過定義一個大數組作為內存堆,數組的大小由宏configTOTAL_HEAP_SIZE指定。第三種內存管理策略與前兩種不同,它不再需要通過數組定義內存堆,而是需要使用編譯器設置內存堆空間,一般在啟動代碼中設置。因此宏configTOTAL_HEAP_SIZE對這種內存管理策略是無效的。

    3.1內存申請:pvPortMalloc()

    [objc]?view plaincopy print?
  • voidvoid?*pvPortMalloc(?size_t?xWantedSize?)??
  • {??
  • voidvoid?*pvReturn;??
  • ??
  • ??
  • ????vTaskSuspendAll();??
  • ????{??
  • ????????pvReturn?=?malloc(?xWantedSize?);??
  • ????????traceMALLOC(?pvReturn,?xWantedSize?);??
  • ????}??
  • ????(?void?)?xTaskResumeAll();??
  • ??
  • ??
  • ????#if(?configUSE_MALLOC_FAILED_HOOK?==?1?)??
  • ????{??
  • ????????if(?pvReturn?==?NULL?)??
  • ????????{??
  • ????????????extern?void?vApplicationMallocFailedHook(?void?);??
  • ????????????vApplicationMallocFailedHook();??
  • ????????}??
  • ????}??
  • ????#endif??
  • ??
  • ??
  • ????return?pvReturn;??
  • }??
  • 3.2 內存釋放:vPortFree()

    [objc]?view plaincopy print?
  • void?vPortFree(?voidvoid?*pv?)??
  • {??
  • ????if(?pv?)??
  • ????{??
  • ????????vTaskSuspendAll();??
  • ????????{??
  • ????????????free(?pv?);??
  • ????????????traceFREE(?pv,?0?);??
  • ????????}??
  • ????????(?void?)?xTaskResumeAll();??
  • ????}??
  • }??
  • 4.heap_4.c

    第四種內存分配方法與第二種比較相似,只不過增加了一個合并算法,將相鄰的空閑內存塊合并成一個大塊。
    與第一種和第二種內存管理策略一樣,內存堆仍然是一個大數組,定義為:
    [objc]?view plaincopy print?
  • static?uint8_t?ucHeap[?configTOTAL_HEAP_SIZE?];??
  • 4.1 內存申請:pvPortMalloc()

    和第二種內存管理策略一樣,它也使用一個鏈表結構來跟蹤記錄空閑內存塊。結構體定義為:
    [objc]?view plaincopy print?
  • typedef?struct?A_BLOCK_LINK??
  • {??
  • ????struct?A_BLOCK_LINK?*pxNextFreeBlock;???/*指向列表中下一個空閑塊*/??
  • ????size_t?xBlockSize;??????????????????????/*當前空閑塊的大小,包括鏈表結構大小*/??
  • }?BlockLink_t;??
  • 與第二種內存管理策略一樣,空閑內存塊也是以單鏈表的形式組織起來的,BlockLink_t類型的局部靜態變量xStart表示鏈表頭,但第四種內存管理策略的鏈表尾保存在內存堆空間最后位置,并使用BlockLink_t指針類型局部靜態變量pxEnd指向這個區域(第二種內存管理策略使用靜態變量xEnd表示鏈表尾),如圖4-1所示。
    第四種內存管理策略和第二種內存管理策略還有一個很大的不同是:第四種內存管理策略的空閑塊鏈表不是以內存塊大小為存儲順序,而是以內存塊起始地址大小為存儲順序,地址小的在前,地址大的在后。這也是為了適應合并算法而作的改變。
    ? 圖4-1:內存堆初始化示意圖 ? ? ? ? 從圖4-1中可以看出,整個有效空間組成唯一一個空閑塊,在空閑塊的起始位置放置了一個鏈表結構,用于存儲這個空閑塊的大小和下一個空閑塊的地址。由于目前只有一個空閑塊,所以空閑塊的pxNextFreeBlock指向指針pxEnd指向的位置,而鏈表xStart結構的pxNextFreeBlock指向空閑塊。xStart表示鏈表頭,pxEnd指向位置表示鏈表尾。
    ? ? ? ? 當申請x字節內存時,實際上不僅需要分配x字節內存,還要分配一個BlockLink_t類型結構體空間,用于描述這個內存塊,結構體空間位于空閑內存塊的最開始處。當然,和第一種、第二種內存管理策略一樣,申請的內存大小和BlockLink_t類型結構體大小都要向上擴大到對齊字節數的整數倍。
    ? ? ? ? 我們先說一下內存申請過程:首先計算實際要分配的內存大小,判斷申請內存合法性,如果合法則從鏈表頭xStart開始查找,如果某個空閑塊的xBlockSize字段大小能容得下要申請的內存,則將這塊內存取出合適的部分返回給申請者,剩下的內存塊組成一個新的空閑塊,按照空閑塊起始地址大小順序插入到空閑塊鏈表中,地址小的在前,地址大的在后。在插入到空閑塊鏈表的過程中,還會執行合并算法:判斷這個塊是不是可以和上一個空閑塊合并成一個大塊,如果可以則合并;然后再判斷能不能和下一個空閑塊合并成一個大塊,如果可以則合并!合并算法是第四種內存管理策略和第二種內存管理策略最大的不同!經過幾次內存申請和釋放后,可能的內存堆如圖4-2所示:
    圖4-2:經過數次內存申請和釋放后,某個內存堆示意圖 有了上面的基礎,我們再來看一下源碼,我把需要注意的要點以注釋的方式放在源碼中,不再單獨對這個函數做講解。函數中會用到幾個局部靜態變量在這里簡單說明一下:
    • xFreeBytesRemaining:表示當前未分配的內存堆大小
    • xMinimumEverFreeBytesRemaining:表示未分配內存堆空間歷史最小值。這個值跟xFreeBytesRemaining有很大區別,只有記錄未分配內存堆的最小值,才能知道最壞情況下內存堆的使用情況。
    • xBlockAllocatedBit:這個變量在第一次調用內存申請函數時被初始化,將它能表示的數值的最高位置1。比如對于32位系統,這個變量被初始化為0x80000000(最高位為1)。內存管理策略使用這個變量來標識一個內存塊是否空閑。如果內存塊被分配出去,則內存塊鏈表結構成員xBlockSize按位或上這個變量(即xBlockSize最高位置1),在釋放一個內存塊時,會把xBlockSize的最高位清零。
    [objc]?view plaincopy print?
  • voidvoid?*pvPortMalloc(?size_t?xWantedSize?)??
  • {??
  • BlockLink_t?*pxBlock,?*pxPreviousBlock,?*pxNewBlockLink;??
  • voidvoid?*pvReturn?=?NULL;??
  • ??
  • ??
  • ????vTaskSuspendAll();??
  • ????{??
  • ????????/*?如果是第一次調用內存分配函數,則初始化內存堆,初始化后的內存堆如圖4-1所示?*/??
  • ????????if(?pxEnd?==?NULL?)??
  • ????????{??
  • ????????????prvHeapInit();??
  • ????????}??
  • ??
  • ??
  • ????????/*?申請的內存大小合法性檢查:是否過大.結構體BlockLink_t中有一個成員xBlockSize表示塊的大小,這個成員的最高位被用來標識這個塊是否空閑.因此要申請的塊大小不能使用這個位.*/??
  • ????????if(?(?xWantedSize?&?xBlockAllocatedBit?)?==?0?)??
  • ????????{??
  • ????????????/*?計算實際要分配的內存大小,包含鏈接結構體BlockLink_t在內,并且要向上字節對齊?*/??
  • ????????????if(?xWantedSize?>?0?)??
  • ????????????{??
  • ????????????????xWantedSize?+=?xHeapStructSize;??
  • ??
  • ??
  • ????????????????/*?對齊操作,向上擴大到對齊字節數的整數倍?*/??
  • ????????????????if(?(?xWantedSize?&?portBYTE_ALIGNMENT_MASK?)?!=?0x00?)??
  • ????????????????{??
  • ????????????????????xWantedSize?+=?(?portBYTE_ALIGNMENT?-?(?xWantedSize?&?portBYTE_ALIGNMENT_MASK?)?);??
  • ????????????????????configASSERT(?(?xWantedSize?&?portBYTE_ALIGNMENT_MASK?)?==?0?);??
  • ????????????????}??
  • ????????????}??
  • ??
  • ??
  • ????????????if(?(?xWantedSize?>?0?)?&&?(?xWantedSize?<=?xFreeBytesRemaining?)?)??
  • ????????????{??
  • ????????????????/*?從鏈表xStart開始查找,從空閑塊鏈表(按照空閑塊地址順序排列)中找出一個足夠大的空閑塊?*/??
  • ????????????????pxPreviousBlock?=?&xStart;??
  • ????????????????pxBlock?=?xStart.pxNextFreeBlock;??
  • ????????????????while(?(?pxBlock->xBlockSize?<?xWantedSize?)?&&?(?pxBlock->pxNextFreeBlock?!=?NULL?)?)??
  • ????????????????{??
  • ????????????????????pxPreviousBlock?=?pxBlock;??
  • ????????????????????pxBlock?=?pxBlock->pxNextFreeBlock;??
  • ????????????????}??
  • ??
  • ??
  • ????????????????/*?如果最后到達結束標識,則說明沒有合適的內存塊,否則,進行內存分配操作*/??
  • ????????????????if(?pxBlock?!=?pxEnd?)??
  • ????????????????{??
  • ????????????????????/*?返回分配的內存指針,要跳過內存開始處的BlockLink_t結構體?*/??
  • ????????????????????pvReturn?=?(?voidvoid?*?)?(?(?(?uint8_t?*?)?pxPreviousBlock->pxNextFreeBlock?)?+?xHeapStructSize?);??
  • ??
  • ??
  • ????????????????????/*?將已經分配出去的內存塊從空閑塊鏈表中刪除?*/??
  • ????????????????????pxPreviousBlock->pxNextFreeBlock?=?pxBlock->pxNextFreeBlock;??
  • ??
  • ??
  • ????????????????????/*?如果剩下的內存足夠大,則組成一個新的空閑塊?*/??
  • ????????????????????if(?(?pxBlock->xBlockSize?-?xWantedSize?)?>?heapMINIMUM_BLOCK_SIZE?)??
  • ????????????????????{??
  • ????????????????????????/*?在剩余內存塊的起始位置放置一個鏈表結構并初始化鏈表成員?*/??
  • ????????????????????????pxNewBlockLink?=?(?voidvoid?*?)?(?(?(?uint8_t?*?)?pxBlock?)?+?xWantedSize?);??
  • ????????????????????????configASSERT(?(?(?(?size_t?)?pxNewBlockLink?)?&?portBYTE_ALIGNMENT_MASK?)?==?0?);??
  • ??
  • ??
  • ????????????????????????pxNewBlockLink->xBlockSize?=?pxBlock->xBlockSize?-?xWantedSize;??
  • ????????????????????????pxBlock->xBlockSize?=?xWantedSize;??
  • ??
  • ??
  • ????????????????????????/*?將剩余的空閑塊插入到空閑塊列表中,按照空閑塊的地址大小順序,地址小的在前,地址大的在后?*/??
  • ????????????????????????prvInsertBlockIntoFreeList(?pxNewBlockLink?);??
  • ????????????????????}??
  • ??????????????????????
  • ????????????????????/*?計算未分配的內存堆空間,注意這里并不能包含內存碎片信息?*/??
  • ????????????????????xFreeBytesRemaining?-=?pxBlock->xBlockSize;??
  • ??????????????????????
  • ????????????????????/*?保存未分配內存堆空間歷史最小值?*/??
  • ????????????????????if(?xFreeBytesRemaining?<?xMinimumEverFreeBytesRemaining?)??
  • ????????????????????{??
  • ????????????????????????xMinimumEverFreeBytesRemaining?=?xFreeBytesRemaining;??
  • ????????????????????}??
  • ??
  • ??
  • ????????????????????/*?將已經分配的內存塊標識為"已分配"?*/??
  • ????????????????????pxBlock->xBlockSize?|=?xBlockAllocatedBit;??
  • ????????????????????pxBlock->pxNextFreeBlock?=?NULL;??
  • ????????????????}??
  • ????????????}??
  • ????????}??
  • ??
  • ??
  • ????????traceMALLOC(?pvReturn,?xWantedSize?);??
  • ????}??
  • ????(?void?)?xTaskResumeAll();??
  • ??
  • ??
  • ????#if(?configUSE_MALLOC_FAILED_HOOK?==?1?)??
  • ????{???/*?如果內存分配失敗,調用鉤子函數?*/??
  • ????????if(?pvReturn?==?NULL?)??
  • ????????{??
  • ????????????extern?void?vApplicationMallocFailedHook(?void?);??
  • ????????????vApplicationMallocFailedHook();??
  • ????????}??
  • ????????else??
  • ????????{??
  • ????????????mtCOVERAGE_TEST_MARKER();??
  • ????????}??
  • ????}??
  • ????#endif??
  • ??
  • ??
  • ????configASSERT(?(?(?(?size_t?)?pvReturn?)?&?(?size_t?)?portBYTE_ALIGNMENT_MASK?)?==?0?);??
  • ????return?pvReturn;??
  • }??
  • 4.2 內存釋放:vPortFree()

    第四種內存管理策略的內存釋放也比較簡單:根據傳入的參數找到鏈表結構,然后將這個內存塊插入到空閑塊列表,需要注意的是在插入過程中會執行合并算法,這個我們已經在內存申請中講過了。最后是將這個內存塊標志為“空閑”、更新未分配的內存堆大小,結束。源代碼如下:
    [objc]?view plaincopy print?
  • void?vPortFree(?voidvoid?*pv?)??
  • {??
  • uint8_t?*puc?=?(?uint8_t?*?)?pv;??
  • BlockLink_t?*pxLink;??
  • ??
  • ??
  • ????if(?pv?!=?NULL?)??
  • ????{??
  • ????????/*?根據參數地址找出內存塊鏈表結構?*/??
  • ????????puc?-=?xHeapStructSize;??
  • ????????pxLink?=?(?voidvoid?*?)?puc;??
  • ??
  • ??
  • ????????/*?檢查這個內存塊確實被分配出去?*/??
  • ????????if(?(?pxLink->xBlockSize?&?xBlockAllocatedBit?)?!=?0?)??
  • ????????{??
  • ????????????if(?pxLink->pxNextFreeBlock?==?NULL?)??
  • ????????????{??
  • ????????????????/*?將內存塊標識為"空閑"?*/??
  • ????????????????pxLink->xBlockSize?&=?~xBlockAllocatedBit;??
  • ??
  • ??
  • ????????????????vTaskSuspendAll();??
  • ????????????????{??
  • ????????????????????/*?更新未分配的內存堆大小?*/??
  • ????????????????????xFreeBytesRemaining?+=?pxLink->xBlockSize;??
  • ????????????????????traceFREE(?pv,?pxLink->xBlockSize?);??
  • ????????????????????/*?將這個內存塊插入到空閑塊鏈表中,按照內存塊地址大小順序?*/??
  • ????????????????????prvInsertBlockIntoFreeList(?(?(?BlockLink_t?*?)?pxLink?)?);??
  • ????????????????}??
  • ????????????????(?void?)?xTaskResumeAll();??
  • ????????????}??
  • ????????}??
  • ????}??
  • }??
  • 如圖4-2所示的內存堆示意圖,如果我們將32字節的“已分配空間2”釋放,由于這個內存塊的上面和下面都是空閑塊,所以在將它插入到空閑塊鏈表的過程在中,會先和“剩余空閑塊1”合并,合并后的塊再和“剩余空閑塊2”合并,這樣組成一個大的空閑塊,如圖4-3所示:
    ? 圖4-3:內存釋放后,會和相鄰的空閑塊合并

    4.3獲取當前未分配的內存堆大小:xPortGetFreeHeapSize()

    在內存申請和內存釋放函數中以及多次提到過變量xFreeBytesRemaining。它就是一個計數器,不能說明內存堆碎片信息。
    [objc]?view plaincopy print?
  • size_t?xPortGetFreeHeapSize(?void?)??
  • {??
  • ????return?xFreeBytesRemaining;??
  • }??
  • 4.4獲取未分配的內存堆歷史最小值:xPortGetFreeHeapSize()

    在內存申請中講解過變量xMinimumEverFreeBytesRemaining,這個函數很有用,通過這個函數我們可以估計出最壞情況下需要多大的內存堆,從而輔助我們合理的設置內存堆大小。
    [objc]?view plaincopy print?
  • size_t?xPortGetMinimumEverFreeHeapSize(?void?)??
  • {??
  • ????return?xMinimumEverFreeBytesRemaining;??
  • }??
  • 5.heap_5.c

    第五種內存管理策略允許內存堆跨越多個非連續的內存區,并且需要顯示的初始化內存堆,除此之外其它操作都和第四種內存管理策略十分相似。
    第一、第二和第四種內存管理策略都是利用一個大數組作為內存堆使用,并且只需要應用程序指定數組的大小(通過宏configTOTAL_HEAP_SIZE定義),數組定義由內存管理策略實現。第五種內存管理策略有些不同,首先它允許跨內存區定義多個內存堆,比如在片內RAM中定義一個內存堆,還可以在片外RAM再定義內存堆;其次,用戶需要指定每個內存堆區域的起始地址和內存堆大小、將它們放在一個HeapRegion_t結構體類型數組中,并需要在使用任何內存分配和釋放操作前調用vPortDefineHeapRegions()函數初始化這些內存堆。
    讓我們看一個例子:假設我們為內存堆分配兩個內存塊,第一個內存塊大小為0x10000字節,起始地址為0x80000000;第二個內存塊大小為0xa0000字節,起始地址為0x90000000。HeapRegion_t結構體類型數組可以定義如下:
    [objc]?view plaincopy print?
  • HeapRegion_t?xHeapRegions[]?=??
  • ?{??
  • ????{?(?uint8_t?*?)?0x80000000UL,?0x10000?},???
  • ????{?(?uint8_t?*?)?0x90000000UL,?0xa0000?},???
  • ????{?NULL,?0?}??????????????????
  • ?};??
  • 兩個內存塊要按照地址順序放入到數組中,地址小的在前,因此地址為0x80000000的內存塊必須放數組的第一個位置。數組必須以使用一個NULL指針和0字節元素作為結束,以便讓內存管理程序知道何時結束。
    定義好內存堆數組后,需要應用程序調用vPortDefineHeapRegions()函數初始化這些內存堆:將它們組成一個鏈表,以xStart鏈表結構開頭,以pxEnd指針指向的位置結束。我們看一下內存堆數組是如何初始化的,以上面的內存堆數組為例,初始化后的內存堆如圖5-1所示(32為平臺,sizeof(BlockLink_t)=8字節)。
    圖5-1:多個非連續內存區用作內存堆初始化示意圖 一旦內存堆初始化之后,內存申請和釋放都和第四種內存管理策略相同,不再單獨分析。

    總結

    以上是生活随笔為你收集整理的FreeRTOS高级篇7---FreeRTOS内存管理分析的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。