RT-Thread移植到S5P4418(二):動態內存管理

內存管理

在RT-Thread中分爲動態內存管理和靜態內存管理。靜態的又稱爲內存池管理,動態的稱爲內存堆管理

內存堆:

官方提供了三種管理方式。

  • 小內存管理。申請時在一塊大的、連續的內存中按需求分割出相匹配的小內存塊;釋放時,歸還給堆管理系統。每個內存塊都包含一個管理用的數據頭,通過雙向鏈表來管理使用塊和空閒塊。
  • slab 管理算法TODO!
  • memheap 管理算法。適用於系統含有多個地址可不連續的內存堆。

內存池:

內存池在創建時先向系統申請一大塊內存,然後分成同樣大小的多個小內存塊,小內存塊直接通過鏈表連接起來(此鏈表也稱爲空閒鏈表)。每次分配的時候,從空閒鏈表中取出鏈頭上第一個內存塊,提供給申請者。


小內存管理算法實現

三個核心函數

  • void rt_system_heap_init(void *begin_addr, void *end_addr);
  • void *rt_malloc(rt_size_t size);
  • void rt_free(void *rmem);

rt_system_heap_init 初始化堆

void rt_system_heap_init(void *begin_addr, void *end_addr)
{
    struct heap_mem *mem;
    rt_ubase_t begin_align = RT_ALIGN((rt_ubase_t)begin_addr, RT_ALIGN_SIZE);
    rt_ubase_t end_align = RT_ALIGN_DOWN((rt_ubase_t)end_addr, RT_ALIGN_SIZE);

    if((end_align > (2 * SIZEOF_STRUCT_MEM)) &&
        ((end_align - 2 * SIZEOF_STRUCT_MEM) >= begin_align)){  // 最少要存兩個heap_mem
        mem_size_aligned = end_align - begin_align - 2 * SIZEOF_STRUCT_MEM;
    }else{
        printf("mem init, error begin address 0x%x, and end address 0x%x\n",
                   (rt_uint32_t)begin_addr, (rt_uint32_t)end_addr);
        
        return;
    }

    heap_ptr = (rt_uint8_t *)begin_align;

    mem = (struct heap_mem *)heap_ptr;
    mem->magic = HEAP_MAGIC;
    mem->used = 0;
    mem->prev = 0;  // pre、next是相對於heap_ptr的偏移量
    mem->next = mem_size_aligned + SIZEOF_STRUCT_MEM;   // mem_size_aligned是減去兩個heap_mem後的大小

    heap_end = (struct heap_mem *)&heap_ptr[mem->next];
    heap_end->magic = HEAP_MAGIC;
    heap_end->used = 1;
    heap_end->prev = mem->next;
    heap_end->next = mem->next;

    rt_sem_init(&heap_sem, "heap", 1, RT_IPC_FLAG_FIFO);   // 初始化內存管理專用信號量

    lfree = (struct heap_mem *)heap_ptr; // lfree記錄最頭部的可用內存塊
}

先說明內存控制塊結構,申請的每個內存塊都包含這樣一個數據頭;

/* 內存控制塊 */
struct heap_mem
{
    /* magic and used flag */
    rt_uint16_t magic;
    rt_uint16_t used;

    rt_size_t next, prev;
};
  • mem.magic被稱爲幻數,他會被初始化成0x1ea0,用於標記這個內存塊是一個內存管理用的內存數據;
  • mem.used表示當前內存塊是否被使用;
  • mem.next和 mem.prev用於雙向鏈表的管理;

初始化內存堆時,需要傳入起始和結束地址,通過編寫鏈接腳本來自定義用於堆的內存大小,以及堆位置。尾部的數據頭表示在這個地址之後沒有內存空間可以分配了。lfree記錄系統當前最頭部的空閒塊。
在這裏插入圖片描述


rt_malloc 申請內存

void *rt_malloc(rt_size_t size){
    struct heap_mem *mem, *mem2;
    rt_size_t ptr, ptr2;

    if(size == 0){
        return RT_NULL;
    }

    size = RT_ALIGN(size, RT_ALIGN_SIZE);

    if(size > mem_size_aligned){
        printf("no memory %d\r\n", size);

        return RT_NULL;
    }

    if(size < MIN_SIZE_ALIGNED){
        size = MIN_SIZE_ALIGNED;
    }

    /* 信號量保護資源 */
    rt_sem_take(&heap_sem, RT_WAITING_FOREVER);

    /* ptr是空閒內存的偏移量,ptr < mem_size_aligned - size說明可能存在能分配的塊 */
    for(ptr = (rt_uint8_t *)lfree - heap_ptr; ptr < mem_size_aligned - size; ptr = ((struct heap_mem *)&heap_ptr[ptr])->next){
        mem = (struct heap_mem *)&heap_ptr[ptr];

        /* mem->next - (ptr + SIZEOF_STRUCT_MEM)是當前內存塊大小 */
        if((!mem->used) && mem->next - (ptr + SIZEOF_STRUCT_MEM) >= size){
            
            /* 如果該內存塊足夠大,將剩餘部分初始化成一個空閒塊 */
            if(mem->next - (ptr + SIZEOF_STRUCT_MEM) >= size + SIZEOF_STRUCT_MEM + MIN_SIZE_ALIGNED){
                ptr2 = ptr + SIZEOF_STRUCT_MEM + size;

                mem2 = (struct heap_mem *)&heap_ptr[ptr2];
                mem2->magic = HEAP_MAGIC;
                mem2->used = 0;
                mem2->prev = ptr;
                mem2->next = mem->next;

                mem->next = ptr2;
                mem->used = 1;

                if(mem2->next != mem_size_aligned + SIZEOF_STRUCT_MEM){
                    ((struct heap_mem *)&heap_ptr[mem2->next])->prev = ptr2;
                }      
            }else{
                mem->used = 1;
            }

            /* 尋找新的、最頭部的空閒塊 */
            if(mem == lfree){
                while(lfree->used && lfree != heap_end)
                    lfree = (struct heap_mem *)&heap_ptr[lfree->next];
            }

            rt_sem_release(&heap_sem);
            
            return (rt_uint8_t *)mem + SIZEOF_STRUCT_MEM;
        }
    }

    rt_sem_release(&heap_sem);

    return RT_NULL;
}

從lfree處往後找大於size的空閒塊,如果找到的內存塊足夠大,將剩餘部分初始化成一個新的空閒塊。同時更新lfree
在這裏插入圖片描述


rt_free 釋放內存

void rt_free(void *rmem){
    struct heap_mem *mem;

    if(rmem == RT_NULL){
        printf("addr is invalid %d\r\n", 0);

        return;
    }

    if((rt_uint8_t *)rmem < heap_ptr || (rt_uint8_t *)rmem >= heap_end){
        printf("addr is invalid %d\r\n", -1);

        return;
    }

    /* 獲取信號量 */
    rt_sem_take(&heap_sem, RT_WAITING_FOREVER);

    mem = (struct heap_mem *)((rt_uint8_t *)rmem - SIZEOF_STRUCT_MEM);
    if (!mem->used || mem->magic != HEAP_MAGIC)
    {
        printf("to free a bad data block: %c\n", ' ');
        printf("mem: 0x%08x, used flag: %d, magic code: 0x%04x\n", mem, mem->used, mem->magic);
    }
    RT_ASSERT(mem->used);
    RT_ASSERT(mem->magic == HEAP_MAGIC);    // 針對上面的if

    /* 標記爲未使用即可 */
    mem->used = 0;
    mem->magic = HEAP_MAGIC;

    if(mem < lfree){
        lfree = mem;
    }

    /* 用於合併前後未使用的內存塊 */
    plug_holes(mem);

    rt_sem_release(&heap_sem);

}

釋放時,修改.used標誌即可,然後調用plug_holes(mem)合併前後未使用的內存塊。


動態內存管理是動態創建線程,以及其他內核應用的前提。

工程文件

rtt2a9_heap
按鍵1申請內存並寫值,按鍵2讀值然後釋放;

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章