RT-thread內核之小內存管理算法


 一、動態內存管理

      動態內存管理是一個真實的堆(Heap)內存管理模塊,可以在當前資源滿足的情況下,根據用戶的需求分配任意大小的內存塊。而當用戶不需要再使用這些內存塊時,又可以釋放回堆中供其他應用分配使用。RT-Thread系統為了滿足不同的需求,提供了兩套不同的動態內存管理算法,分別是小內存管理算法SLAB內存管理算法。小堆內存管理模塊主要針對系統資源比較少,一般用於小於2M內存空間的系統;而SLAB內存管理模塊則主要是在系統資源比較豐富時,提供了一種近似多內存池管理算法的快速算法。

      兩種內存管理模塊在系統運行時只能選擇其中之一或者完全不使用動態堆內存管理器。這兩種管理模塊提供的API接口完全相同。因為動態內存管理器要滿足多線程情況下的安全分配,會考慮多線程間的互斥問題,所以請不要在中斷服務例程中分配或釋放動態內存塊。因為它可能會引起當前上下文被掛起等待。

二、小內存管理算法

      本文主要介紹小內存管理算法,至於SLAB內存管理算法則在后續文章中介紹。小內存管理算法是一個簡單的內存分配算法。初始時,它是一塊大的內存。當需要分配內存塊時,將從這個大的內存塊上分割出相匹配的內存塊,然后把分割出來的空閑內存塊還回給堆管理系統中。每個內存塊都包含一個管理用的數據頭,通過這個頭把使用塊與空閑塊用雙向鏈表的方式鏈接起來,如 內存塊鏈表圖所示:

內存管理的在表現主要體現在內存的分配與釋放上,小型內存管理算法可以用以下例子體現出來。

                                                                                            

 

      空閑鏈表指針lfree初始指向32字節的內存塊。當用戶線程要再分配一個64字節的內存塊時,但此lfree指針指向的內存塊只有32字節並不能滿足要求,內存管理器會繼續尋找下一內存塊,當找到再下一塊內存塊,128字節時,它滿足分配的要求。因為這個內存塊比較大,分配器將把此內存塊進行拆分,余下的內存塊(52字節)繼續留在lfree鏈表中。另外,在每次分配內存塊前,都會留出12字節數據頭用於magic,used信息及鏈表節點使用。返回給應用的地址實際上是這塊內存塊12字節以后的地址,前面的12字節數據頭是用戶永遠不應該碰的部分。(注:12字節數據頭長度會與系統對齊差異而有所不同)。釋放時則是相反的過程,但分配器會查看前后相鄰的內存塊是否空閑,如果空閑則合並成一個大的空閑內存塊。

      數據結構:在src/mem.c中

#define HEAP_MAGIC 0x1ea0
struct heap_mem
{
    /* magic and used flag */
    rt_uint16_t magic;     //如果此內存塊被分配了,則(在rt_malloc中設置)置0x1ea0。標志該內存塊為一個內存管理系統使用的動態內存數據塊,類似於一個內存保護字:如果這個區域被改寫,那么也就意味着這塊內存塊被非法改寫                             (正常情況下只有內存管理系統才會訪問它)
    rt_uint16_t used;      //0:未分配;1:已分配  

    rt_size_t next, prev;  //后一內存塊首地址(包含內存塊控制結構),前一內存塊首地址(包含內存塊控制結構)。注意這里不再是rt_list_t類型(鏈表類型),它們直接賦值為內存地址
};

三、小內存管理算法函數接口:在src/mem.c中

初始化動態內存堆:
void rt_system_heap_init(void *begin_addr, void *end_addr);
在使用堆內存RT_USING_HEAP時,必須要在系統初始化的時候進行堆內存的初始化。這個函數會把參數begin_addr,end_addr區域的內存空間作為內存堆來使用。
由源代碼可知,初始化時小內存管理算法通過傳進來的起始地址和末尾地址將動態堆內存初始化為兩個內存塊:第一個內存塊指向動態堆內存首地址,可用空間為整個可分配的內存(不包含兩個內存控制塊本身所占大小,即減去24字節),此內存塊下一指針指向末尾內存控制塊;第二個內存塊指向最末尾的一個內存控制塊,可用空間大小為0,此內存塊前一指針和后一指針都指向本身。

分配內存塊:
void *rt_malloc(rt_size_t size);
rt_malloc函數會從系統堆空間中找到合適大小的內存塊,然后把內存塊可用地址返回給用戶。

重分配內存塊:
void *rt_realloc(void *rmem, rt_size_t newsize);
在已分配內存塊的基礎上重新分配內存塊的大小(增加或縮小),在進行重新分配內存塊時,原來的內存塊數據保持不變(縮小的情況下,后面的數據被自動截斷)。
由代碼可知,如果當前內存塊可用內存比較充裕時,將分割成兩塊,后一塊分割出來后會嘗試與前后內存塊合並。 分配多塊內存:
void *rt_calloc(rt_size_t count, rt_size_t size); 從內存堆中分配連續內存地址的多個內存塊,返回的指針指向第一個內存塊的地址,並且所有分配的內存塊都被初始化成零。 釋放內存: void rt_free(void *rmem); 用戶線程使用完從內存分配器中申請的內存后,必須及時釋放,否則會造成內存泄漏,rt_free函數會把待釋放的內存換回給堆管理器中。在調用這個函數時用戶需傳遞待釋放的內存塊指針,如果是空指針直接返回。
內存合並:
static void plug_holes(struct heap_mem *mem);
此函數在重分配內存時調用,將分割出來的后一部分嘗試與前后內存塊合並;當釋放內存時,算法將檢查待釋放內存的前一內存塊和后一內存塊,如果為空閑則合並。

四、算法總結

      小內存管理算法從整體上來講,是將一片內存初始化為靜態鏈表來實現的,初始化時只有兩個內存塊:

      第一塊除了包含內存塊控制塊(占用12字節)外,還包含待分配的空間,這個空間就是mem_size_aligned,它是指此算法可用來作分配的動態堆內存總大小,任何待分配的內存都不能比它還大,否則超過極限

      第二塊只包含內存控制塊本身(占用12字節),不包含待分配的空間,它作為鏈表尾,且設置使用標志used為1(永久使用)

      整個算法中還有一空閑指針lfree,始終指向動態內存堆中剩余可用空間的第一個空閑內存塊,初始化時指向動態內存堆起始地址,該指針變量在分配內存和合並內存時會不斷更新。接下來就是分配內存了,分配內存時首先從空閑內存所指向的節點開始掃描,一旦掃描到大小滿足的節點,則返回此節點,若此節點所指向的空間足夠大,大到還有足夠空間分配另一內存塊(只含內存塊控制結構)時,則分割此節點指向的內存為兩塊,前一塊內存可用空間user data首地址返回,后一塊內存設置為未分配(空閑內存); 當釋放內存時,算法將檢查待釋放內存的前一內存塊和后一內存塊,如果為空閑則合並。

 


免責聲明!

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



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