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读值然后释放;

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