rt-thread-------内存管理(内存堆)

news/2024/12/23 4:44:16/

系列文章目录

rt-thread 之 fal移植
rt-thread 之 生成工程模板
STM32------串口理论篇
rt-thread------串口V1版本(一)配置
rt-thread------串口V1版本(二)发送篇
rt-thread------串口V1版本(三)接收篇


rt-thread内存管理

  • 系列文章目录
  • 前言
  • 1、内存堆的初始化
  • 2.内存申请API
    • rt_malloc
    • rt_realloc
      • rt_calloc
  • 3.内存释放API
    • rt_free
  • 4.内存堆的三种策略
    • 4.1小内存管理法
    • 4.2 slab 管理算法
    • 4.3 memheap 管理算法


前言

简述堆和栈
堆(stack):由编译器自动分配释放
栈(heap):一般由程序员分配和释放


1、内存堆的初始化

rt-thread的内存分配是从ZI段结尾处到内存尾部的空间作为内存堆,如下图所示:
在这里插入图片描述
rt-thread中是通过下面函数实现的内存的初始化

void rt_system_heap_init(void *begin_addr, void *end_addr);

实际会在rt_hw_board_init()函数中被调用

rt_system_heap_init((void *)HEAP_BEGIN, (void *)HEAP_END);

看看这个HEAP_BEGIN 和HEAP_END的定义
先看HEAP_END的定义

#define HEAP_END        STM32_SRAM_END

套了另外一个宏STM32_SRAM_END
实际上本质是下面两个宏定义,我的是STM32F103ZET6,根据芯片手册是64K的SRAM,所以结束地址就是芯片内存最后的地址这一点不难理解。

/* Internal SRAM memory size[Kbytes] <8-64>, Default: 64*/
#define STM32_SRAM_SIZE      64
#define STM32_SRAM_END       (0x20000000 + STM32_SRAM_SIZE * 1024)

再看看HEAP_BEGIN的定义

extern int Image$$RW_IRAM1$$ZI$$Limit;
#define HEAP_BEGIN      ((void *)&Image$$RW_IRAM1$$ZI$$Limit)

Image$$RW_IRAM1$$ZI$$Limit这是一个外部变量,但是我通过工程无法找到这个变量,但是我在kei工程的生成的map文件中找到了这个变量
在这里插入图片描述
所以我猜这是链接器分配ZI字段结束的地址。
以下只针对ARM_CC编译器
在这里插入图片描述
rt_system_heap_init函数展开的细节这里不深究了,如果非要刨根问底的话可以参考这篇文章

当使用memheap内存分配策略时还需要调用,对不连续内存块分别初始化,并且加入 memheap_item 链表。

rt_err_t rt_memheap_init(struct rt_memheap  *memheap,const char  *name,void        *start_addr,rt_uint32_t size)

2.内存申请API

rt_malloc

API

void *rt_malloc(rt_size_t nbytes);

使用方法和逻辑的malloc一样。rt_malloc函数会从系统堆空间中找到合适大小的内存块,若无法申请则返回RT_NULL所以程序中对此函数的非空判断还是不能省略的。

rt_realloc

API

void *rt_realloc(void *rmem, rt_size_t newsize);

在进行重新分配内存块时,原来的内存块数据保持不变(缩小的情况下,后面的数据被自动截断)。若重新分配的内存块变大,则会在rmem内存后面增加未被初始化的内存块。

rt_calloc

API

  void *rt_calloc(rt_size_t count, rt_size_t size);

从内存堆中分配连续内存地址的多个内存块。

3.内存释放API

rt_free

API

void rt_free (void *ptr);

rt_free 函数会把待释放的内存还回给堆管理器中。在调用这个函数时用户需传递待释放的内存块指针,如果是空指针直接返回

4.内存堆的三种策略

无论选择哪个策略,都会可以使用上面的API接口。对内存进行操作。

4.1小内存管理法

初始时,它是一块大的内存。当需要分配内存块时,将从这个大的内存块上分割出相匹配的内存块,然后把分割出来的空闲内存块还回给堆管理系统中。每个内存块都包含一个管理用的数据头,通过这个头把使用块与空闲块用双向链表的方式链接起来。
在这里插入图片描述
rt_free()时会把used设置成未使用,下次申请内存时会先遍历到没使用的内存块,然后判断申请的内存是否小于分配好的未使用的内存块。若小于直接使用,并且在数据后面将剩余内存生成一个链表头,标记为未使用。若大于则继续遍历,若没有找到合适大小的内存堆,则在最后未分配的内存堆中分配该大小的内存。
这种小内存管理方法若频繁rt_malloc()申请不同大小的内存堆然后再rt_free()必然产生大量内存碎片。再看看rt_free()函数。

/*** @brief This function will release the previously allocated memory block by*        rt_mem_alloc. The released memory block is taken back to system heap.** @param rmem the address of memory which will be released.*/
void rt_smem_free(void *rmem)
{struct rt_small_mem_item *mem;struct rt_small_mem *small_mem;if (rmem == RT_NULL)return;RT_ASSERT((((rt_ubase_t)rmem) & (RT_ALIGN_SIZE - 1)) == 0);/* Get the corresponding struct rt_small_mem_item ... */mem = (struct rt_small_mem_item *)((rt_uint8_t *)rmem - SIZEOF_STRUCT_MEM);RT_DEBUG_LOG(RT_DEBUG_MEM,("release memory 0x%x, size: %d\n",(rt_ubase_t)rmem,(rt_ubase_t)(mem->next - ((rt_uint8_t *)mem - small_mem->heap_ptr))));/* ... which has to be in a used state ... */small_mem = MEM_POOL(mem);RT_ASSERT(small_mem != RT_NULL);RT_ASSERT(MEM_ISUSED(mem));RT_ASSERT(rt_object_get_type(&small_mem->parent.parent) == RT_Object_Class_Memory);RT_ASSERT(rt_object_is_systemobject(&small_mem->parent.parent));RT_ASSERT((rt_uint8_t *)rmem >= (rt_uint8_t *)small_mem->heap_ptr &&(rt_uint8_t *)rmem < (rt_uint8_t *)small_mem->heap_end);RT_ASSERT(MEM_POOL(&small_mem->heap_ptr[mem->next]) == small_mem);/* ... and is now unused. */mem->pool_ptr = MEM_FREED();
#ifdef RT_USING_MEMTRACErt_smem_setname(mem, "    ");
#endif /* RT_USING_MEMTRACE */if (mem < small_mem->lfree){/* the newly freed struct is now the lowest */small_mem->lfree = mem;}small_mem->parent.used -= (mem->next - ((rt_uint8_t *)mem - small_mem->heap_ptr));/* finally, see if prev or next are free also */plug_holes(small_mem, mem);
}

rt_free()的最后有个plug_hoes()其作用就是检查释放内存的前面一个内存块和后面一个内存块。若未使用则将其合并,生成一个新的内存块。有了内存块合并可以大大减少内存碎片的产生。

4.2 slab 管理算法

RT-Thread 的 slab 分配器是在 DragonFly BSD 创始人 Matthew Dillon 实现的 slab 分配器基础上,针对嵌入式系统优化的内存分配算法。最原始的 slab 算法是 Jeff Bonwick 为 Solaris 操作系统而引入的一种高效内核内存分配算法。

RT-Thread 的 slab 分配器实现主要是去掉了其中的对象构造及析构过程,只保留了纯粹的缓冲型的内存池算法。slab 分配器会根据对象的大小分成多个区(zone),也可以看成每类对象有一个内存池
在这里插入图片描述
slab算法堆单片机sram要求比较高,一般>2M的才会使用,详细的算法以及说明可以看官方的手册

4.3 memheap 管理算法

当使用的芯片有两个块SRAM(如STM32F427)或者外挂SRAM时可以使用此种内存管理方法。
在这里插入图片描述

rt_err_t rt_memheap_init(struct rt_memheap  *memheap,const char  *name,void        *start_addr,rt_uint32_t size)

如果有多个不连续的 memheap 可以多次调用该函数将其初始化并加入 memheap_item 链表。


http://www.ppmy.cn/news/739035.html

相关文章

请教:请问怎样用键盘移动桌面图标到任意位置?

请教&#xff1a;请问怎样用键盘移动桌面图标到任意位置&#xff1f;

如何查看键盘上面的键值?

有同学问我如何查看某一个键值&#xff0c;于是我专门写这篇博客分享一个工具&#xff0c;对于学习C/C初级简单制作某一个游戏还是可以的。 下载地址&#xff1a; https://dearbear.lanzoui.com/i8QKqhbfv5e

如何获取键盘按下的键

<script>//页面的任何的位置.按下键盘,获取按键的值document.onkeydownfunction (e) {console.log(e.keyCode);//每一个键的keycode都不一样}; </script>使用案例&#xff1a; <script>document.addEventListener("keydown",function (e) {switch…

MapKeyboard键盘改键

MapKeyboard键盘改键.exe

页面的任何位置,按下键盘,获取按键的值

触发onkeydown事件&#xff0c;获取keyCode&#xff0c;可以用作相应的判断&#xff0c;键码值网上查一下 document.onkeydownfunction(e){console.log(e.keyCode);//输出不同按键对应的键码值&#xff0c;来判断点了哪个按键}拓展 <script>// document.onkeydownfunctio…

解决Visio中对象不能通过键盘方向键微调位置

解决Visio中对象不能通过键盘方向键微调位置 有时候通过键盘方向键对Visio图进行位置的细微修改时&#xff0c;会出现整个画布移动而不是对象的移动。这时&#xff0c;你该检查Scroll Lock 是否被按下。 不同的键盘的scroll lock的解锁都有点区别。观察键盘中带有“Scroll”或…

AHK 键盘控制鼠标点击屏幕不同位置

按键控制鼠标点击屏幕特定位置 按键控制重设屏幕点击位置 按键显示当前鼠标在屏幕上的位置 V1.0代码功能&#xff1a; 按 A 和 D 分别对应点击两个单独的屏幕坐标按 Q 和 E分别对应重新设置两个单独的屏幕坐标位置&#xff0c;并弹窗显示拾取到的光标坐标 ; 按 A 和 D 分别对…

Windows键位映射

1 键盘扫描码&#xff08;scancode&#xff09; 键盘扫描码是键盘发送给计算机的一项数据&#xff0c;用来报告哪个键位被按下常用的键位扫描码 表中扫描码与注册表值是相反的&#xff0c;例如Capslock的键位扫描码是00 3A但是在注册表中值要用3A 00表示 2 键盘注册表的位置 …