RT-Thread动态内存堆管理

首先讲一下RT-Thread的API的三个函数

1、rt_malloc()

填入你需要的字(32bit,4个字节)个数,然后返回分配后空间的指针

 void *rt_malloc(rt_size_t size)

2、rt_realloc()

用于给已经被分配内存补充分配内存

3、rt_free()

所有的动态内存分配使用完后,都要使用这个函数分配

 小内存管理算法

  • 内核启动时,调用rt_system_heap_init()给内存堆规划空间
  • 内存堆按分配需求会拆分为多个内存块,每一个内存块在首部空间都包含一个数据头,该数据头的双向链表将内存块链接在一起。内存堆末端使用一个数据头heap_end作为结尾标记

  •  lfree作为全局变量,时钟指向第一个空闲块,方便分配内存块时快速从第一个空闲块作为入口开始遍历整个内存块链表;全局变量heap_ptr时钟指向内存堆起始地址

  • 当用户调用rt_malloc()需要申请内存时,系统会从lfree链表头开始遍历,寻找到合适大小空闲内存块,如果该内存块足够大,会将该内存块进行拆分,剩余的部分单独作为一个新的空闲内存块,同时将lfree指向新的第一块空闲内存块

 4.1内存块数据头

struct heap_mem
{
    //幻数,设定固定值0x1ea0用于标记控制块
    rt_uint16_t magic;
    //标记该内存块是否空闲
    rt_uint16_t used;
    //类似双向链表指针,不过该值记录的是地址偏移量
    rt_size_t next, prev;
    //记录哪个线程申请的该内存块
#ifdef RT_USING_MEMTRACE
    rt_uint8_t thread[4];   
#endif /* RT_USING_MEMTRACE */
};
  • 每一个内存块的前面部分空间作为数据头,记录该数据库的相关信息以及链接上下内存块
  • 另外内存堆结尾处会占用一个数据头空间,作为heap_end

4.2统一的API接口

内核对于三种动态内存堆管理算法是使用一致的API接口定义。典型使用如下:

  • 在内核启动时初始化系统堆内存空间:rt_system_heap_init()
  • 申请任意大小的动态内存:rt_malloc()
  • 释放动态内存 rt_free()

4.3 rt_syatem_heap_init()

void rt_system_heap_init(void *begin_addr, void *end_addr)
{
    //1 将内存堆的起始和结束地址按设定进行字节对齐(有的CPU必须对齐访问内存,有的CPU可以非对齐访问内存但效率低)
    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);
    
    //2 初始化的内存堆由于未分配,只有一个内存块(包含一个数据头)以及内存堆末端的数据头(用于标记内存堆的结束)
    //  所以一个内存堆最小空间需容纳下两个数据头
    if ((end_align > (2 * SIZEOF_STRUCT_MEM)) &&((end_align - 2 * SIZEOF_STRUCT_MEM) >= begin_align))
    {
        //2.1 计算初始堆实际数据区的大小。(后续数据区会越来越小,因为每新分配一个内存块需要一个数据头的空间)
        mem_size_aligned = end_align - begin_align - 2 * SIZEOF_STRUCT_MEM;
    }
    else
    {
        //2.2 内存堆空间太小初始化失败
        rt_kprintf("mem init, error begin address 0x%x, and end address 0x%x\n",
                   (rt_ubase_t)begin_addr, (rt_ubase_t)end_addr);
        return;
    }
    
    //3 初始化第一个内存块的数据头
    heap_ptr = (rt_uint8_t *)begin_align;
    mem        = (struct heap_mem *)heap_ptr;
    mem->magic = HEAP_MAGIC;
    //3.1 该值是记录下一个内存块的地址偏移量,由于未做任何分配,整个数据区都属于第一个内存块,所以直接指向heap_end
    mem->next  = mem_size_aligned + SIZEOF_STRUCT_MEM;
    mem->prev  = 0;
    mem->used  = 0;
    
    //4 初始化内存堆结束数据头,heap_end是个全局变量,可用于内存堆使用时的溢出判断等
    heap_end        = (struct heap_mem *)&heap_ptr[mem->next];
    heap_end->magic = HEAP_MAGIC;
    heap_end->used  = 1;
    //4.1 双向链表都指向自己本身heap_end
    heap_end->next  = mem_size_aligned + SIZEOF_STRUCT_MEM;
    heap_end->prev  = mem_size_aligned + SIZEOF_STRUCT_MEM;
    
    //5 初始化信号量,用于内存申请释放时的线程互斥
    rt_sem_init(&heap_sem, "heap", 1, RT_IPC_FLAG_PRIO);
    
    //6 lfree是个全局链表头,始终指向第一个空闲块
    lfree = (struct heap_mem *)heap_ptr;
}
  • 用户通过设定起始地址HEAP_ADDR_ATART和结束地址HEAD_ADDR_END来初始化内存堆空间
  • 初始化的内存堆只有一整块内存块(包含数据头)和内存堆结束数据头heap_end
  • 通过数据头的双向链表完成链接,由于只有一个内存块,所以该内存块的链表指针next直接指向heap_end
  • 全局变量lfree时钟指向低于i个空闲内存块的数据头,方便分配内存时快速从第一个空闲块开始遍历
  • 全局变量heap_ptr始终指向内存堆起始地址

4.4 rt_malloc()

void *rt_malloc(rt_size_t size)
{
    //1 申请内存大小按字节对齐
    size = RT_ALIGN(size, RT_ALIGN_SIZE);
    
    //2 mem_size_aligned在rt_system_heap_init()初始化的,表示可申请的最大空间
    if (size > mem_size_aligned)
    {
        RT_DEBUG_LOG(RT_DEBUG_MEM, ("no memory\n"));
        return RT_NULL;
    }
    
    //3 限定申请内存的最小值(这个值设定有什么测试依据吗)
    if (size < MIN_SIZE_ALIGNED)
        size = MIN_SIZE_ALIGNED;
        
    //4 获取信号量,如果有其他线程在释放或者申请内存块,则线程挂起,避免操作冲突
    rt_sem_take(&heap_sem, RT_WAITING_FOREVER);
    
    //5(lfree始终指向第一个空闲块,heap_ptr始终指向内存堆起始地址)
    // 从第一个空闲块开始逐一遍历
    for (ptr = (rt_uint8_t *)lfree - heap_ptr;
         ptr < mem_size_aligned - size;
         ptr = ((struct heap_mem *)&heap_ptr[ptr])->next)
    {
        //5.1 获取内存块的数据头
        mem = (struct heap_mem *)&heap_ptr[ptr];   
        
        //5.2 如果该内存未使用并且空间最够大,否则则遍历下一个数据块
        if ((!mem->used) && (mem->next - (ptr + SIZEOF_STRUCT_MEM)) >= size)
        {
            //5.2.1 如果该空闲块分配出申请所需的空间,还有足够多余的空间(MIN_SIZE_ALIGNED)
            //则将剩余空间新建立空闲内存块,并与其他空闲块链接起来
            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->next = mem->next;
                mem2->prev = ptr;     
                
                //将其链接起来
                mem->next = ptr2;
                mem->used = 1;
                //这个是判断mem2->next指向的空间是否堆结束标记,如果不是就需要将其链接(堆结束标记数据头是链接自己本身)
                if (mem2->next != mem_size_aligned + SIZEOF_STRUCT_MEM)
                {
                    ((struct heap_mem *)&heap_ptr[mem2->next])->prev = ptr2;
                }      
            }
            else
            {
                //5.2.2 如果剩余空间比较小则不拆分,直接将整个内存块分配出去(虽然会存在一定内存浪费)
                mem->used = 1;                            
            }
            
            //5.2.3 将lfree重新指向新的第一个空闲内存块
            if (mem == lfree)
            {
                while (lfree->used && lfree != heap_end)
                    lfree = (struct heap_mem *)&heap_ptr[lfree->next];
            }
            
            //5.2.4 释放信号量,并返回内存块地址(跳过数据头)
            rt_sem_release(&heap_sem);
            return (rt_uint8_t *)mem + SIZEOF_STRUCT_MEM;
        }                   
    }
    
    rt_sem_release(&heap_sem);
    return RT_NULL;
}
  •  该函数通过信号量进行线程互斥,肯定导致挂起,因此不可以在中断上下文中调用
  • 内核会限定申请内存的最大长度mem_size_aligned和最小长度MIN_SIZE_ALIGNED
  • 从lfree指向的第一块空闲内存块开始遍历,找到空间足够的空闲块。将该内存块按一定条件进行分割,剩余的空间做为新的空闲内存块。

4.5 rt_free()

 

void rt_free(void *rmem)
{
    //1 获取该内存块的数据头
    mem = (struct heap_mem *)((rt_uint8_t *)rmem - SIZEOF_STRUCT_MEM);
    
    //2 获取信号量,如果有其他线程在释放或者申请内存块,则线程挂起,避免操作冲突
    rt_sem_take(&heap_sem, RT_WAITING_FOREVER);
    
    //3 更新数据头
    mem->used  = 0;
    mem->magic = HEAP_MAGIC;
    
    //4 更新lfree,保证lfree始终指向第一个空闲块
    if (mem < lfree)
    {
        lfree = mem;
    }
    
    //5 查看相邻内存块是否空闲,如果是的则尝试进行合并
    plug_holes(mem);
    
    rt_sem_release(&heap_sem);
}
  • 整个过程会通过信号量进行线程互斥,可能导致挂起
  • 更新内存块为空闲块,更新lfree保证其始终指向第一个空闲块
  • 查看相邻内存块是否空闲,尝试进行合并

### 回答1: RT-Thread API参考手册是目前广受欢迎的国产实时操作系统RT-Thread的一本重要文献。该手册包含了RT-Thread操作系统中所有关键的API函数,同时具有简洁明了、易于查找和使用的特点。 RT-Thread操作系统采用微内核、分层架构,并提供了丰富的驱动框架、网络协议栈、文件系统、多任务调度、内存管理、线程通信机制等功能,因此在应用开发中常常需要使用大量的API。RT-Thread API参考手册详细描述了API函数的输入、输出参数、返回值、调用顺序等信息,提供了一种快速高效的API查询方式。 该手册设计简洁,方便查找和使用。比如,每个API函数都包含了目录号、函数原型、调用方式、参数描述、返回值描述、示例等详细信息,可为开发者提供全方位的参考。同时,该手册采用了直观的示例代码和图表展示,进一步提高了API的理解效率。 总之,RT-Thread API参考手册是一本极具实用价值的参考文献,为开发者提供了方便快捷的API查询方式,帮助开发者更加高效地进行应用开发。 ### 回答2: RT-Thread是一个开放源代码的实时操作系统。它被设计用于嵌入式设备,具有小巧、高效、灵活等特点,广泛应用于智能家居、医疗器械、工业控制等领域。 RT-Thread的API参考手册详细介绍了RT-Thread支持的API接口,包括线程、信号量、消息队列、时间等等。这些API提供了丰富的功能,可以方便地完成各种任务。 其中,线程机制是RT-Thread的核心功能。RT-Thread提供了很多关于线程的API接口,例如线程创建、线程删除、线程挂起、线程恢复等等。通过这些接口,可以很方便地创建、管理线程,实现多任务并行执行。 此外,RT-Thread也提供了丰富的信号量、消息队列等机制,这些机制可以帮助开发者实现并发操作、资源共享等功能,提高系统的可靠性和可维护性。 RT-Thread的API参考手册还介绍了事件、定时器等机制,以及底层HAL接口、操作系统钩子等内容,这些内容可以满足不同领域、不同应用的需求。 总之,RT-Thread的API参考手册提供了完善的API接口和使用说明,使得开发者可以快速、方便地开发出高效、可靠的嵌入式系统。 ### 回答3: RT-thread 是一款高效实时性嵌入式操作系统,相较于嵌入式领域常见的裸机编程方式,它提供了更多的操作系统级别的抽象和接口,帮助嵌入式开发者更好地在系统中实现各种需要的功能。 其中,RT-thread API 参考手册可以作为 RT-thread 编程的宝典,详细地介绍了 RT-thread 提供的各种 API 接口,并且提供了对应的函数原型,使用说明以及示例代码。这些接口包括但不限于线程、信号量、消息队列、定时器、事件标志组等等,可以在嵌入式系统的开发中帮助开发者快速完成各种任务和功能。 除此之外,RT-thread API 参考手册还提供了 RT-thread 内核的系统组件、文件系统、网络协议栈等方面的详细介绍,方便开发者对 RT-thread 操作系统的整体构架和实现细节进行深入了解和使用。 总之,RT-thread API 参考手册是 RT-thread 编程不可或缺的参考文献,能够为 RT-thread 开发者提供全面的支持和帮助,让他们更好地完成系统开发和维护。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值