RT-Thread快速入门-内存池

简介: RT-Thread快速入门-内存池

这篇文章继续介绍 RT-Thread 内存管理剩下的部分——内存池。

为何引入内存池?

内存堆虽然方便灵活,但是存在明显的缺点:

  • 分配效率低。每次分配内存的时候,都需要查找空闲内存块。
  • 容易产生内存碎片。

为了规避这两个问题,RT-Thread 提供了内存池(Memory Pool)的管理机制。

第一:理解内存池

内存池用于分配大小相同的小内存块,可以极大地提高内存分配和释放的速度,且避免内存碎片。

内存池的其他优点:支持线程挂起。内存池无空闲内存块时,申请线程会被挂起,直到有可用内存块。

简单理解,就是将相同大小的内存块通过某种方式放在一起,就好比将各个内存块放在类似于水池的容器里,需要用的时候,就从这个池子里取。

1. 内存块工作机制

使用内存池需要以下几个步骤:

  • 创建内存池。先向系统申请一块大的内存。
  • 分割大内存块。将申请成功过的大内存块,分成多个同样大小的小内存块。
  • 连接小内存块。以链表的形式,将各个小内存块连接起来。
  • 分配内存块。在用户申请内存块时,从空闲链表中取出第一个内存块给申请者。

内存池工作机制如下图所示。

 

注意:内存池一旦创建并初始化完成后,其内部的内存块大小就固定了,不能再做调整。

2. 内存池控制块

RT-Thread 通过内存池控制块来操作和管理内存池,内存控制块结构体用于存放内存池的一些信息,包括:内存池数据域起始地址、内存块大小和内存块列表,还有内存块与内存块之间连接用的链表结构等等。

其具体的定义由 struct rt_mempool 表示,如下:

struct rt_mempool
{
  struct rt_object parent;  /* 继承自 rt_object 类 */
  void *start_address;     /* 内存池数据区域开始地址 */
  rt_size_t size;          /* 内存池数据区域大小 */
  rt_size_t block_size;    /* 内存块大小 */
  rt_uint8_t *block_list;  /* 内存块列表 */
  /* 内存池数据区域中能够容纳的最大内存块数 */
  rt_size_t block_total_count;
  /* 内存池中空闲的内存块数 */
  rt_size_t block_free_count;
  /* 因为内存块不可用而挂起的线程列表 */
  rt_list_t suspend_thread;
  /* 因为内存块不可用而挂起的线程数 */
  rt_size_t suspend_thread_count;
};
typedef struct rt_mempool* rt_mp_t;

其中,rt_mp_t  表示的是内存池控制块的句柄,即指向内存池结构体的指针。

结构体成员 suspend_thread  形成了一个申请线程等待列表,即当内存池中无可用内存块时,其申请线程允许等待,申请线程将挂起在 suspend_thread  链表上。

第二:内存池管理

RT-Thread 提供了管理内存池的函数接口,包含:

  • 创建 / 初始化内存池
  • 申请内存块
  • 释放内存块
  • 删除 / 脱离内存池

 

老规矩,本文详细讲解常用的几种函数接口,其他不常用的接口简单介绍,了解即可。

1. 动态创建内存池

RT-Thread 创建内存池,与创建其他内核对象类似,具有两种方式:动态创建、静态初始化。

动态创建内存池是由内核负责完成分配内存池需要的内存资源,包括内存池控制块和内存池缓冲区。创建内存池的函数原型如下:

rt_mp_t rt_mp_create(const char* name,
                      rt_size_t block_count,
                      rt_size_t block_size)

参数 name 内存池的名字;block_count 为内存池中小内存块的个数;block_size 为内存块的大小,单位字节。

创建成功,则返回内存池对象句柄;否则,返回 RT_NULL

调用 rt_mp_create() 可以创建一个与需求的内存块大小、数目相匹配的内存池 。该函数从系统中申请一个内存池对象,自动分配内存池控制块,然后从内存堆中分配一个内存缓冲,该缓冲区大小由内存块数目与块大小计算得到的。

申请的资源准备好后,初始化内存池控制块,然后将内存缓冲区组织成可用于分配的空闲块链表。

注意:动态创建内存池时,需要内存堆资源能够满足要求。

2. 静态初始化内存池

静态方式创建的内存池,所需要的内存资源是由用户自己分配的。需要用户定义一个内存池控制块,并且指定一个内存缓冲区,用于组织内存池。然后调用如下函数,初始化内存池:

rt_err_t rt_mp_init(rt_mp_t mp, const char *name,
                    void *start, rt_size_t size,
                    rt_size_t block_size)

此函数中,参数 mp 为内存池控制块指针;start 为用户指定的缓冲区首地址。size 为内存池数据区域的大小。其他参数与 rt_mp_create() 相同。

初始化成功,返回 RT_EOK;否则,返回 -RT_ERROR

该函数对内存池进行初始化,将内存池用到的内存空间组织成可用于分配的空闲块链表。内存池中内存块的个数为

size / (block_size + 指针大小)

计算结果向下取整。

3. 分配内存块

内存池创建成功了。接下来就是如何用内存池:分配内存块和释放内存。

从指定的内存池中申请一个内存块,RT-Thread 的函数接口如下:

void *rt_mp_alloc (rt_mp_t mp, rt_int32_t time)

参数 mp 为内存池句柄,即内存池控制块指针;time 为申请超时时间。

分配成功,则返回内存块地址;否则,返回 RT_NULL

线程调用此函数分配内存块,如果内存池中有可用的内存块,则从内存池的空闲链表上取下一个内存块,并减少空闲块数目,将这个内存块的地址返回给调用线程。

若内存池中没有空闲内存块,则判断超时时间:

  • 超时时间为零,则立即返回 RT_NULL
  • 超时时间大于零。则把调用线程挂起在这个内存池对象上。

4. 释放内存块

内存块使用完毕之后,必须将其释放掉,否则会造成内存泄漏。释放内存块的函数接口如下:

void rt_mp_free (void *block)

参数 block 为内存块指针。

调用该函数释放内存块过程中,首先通过内存块指针计算得到该内存块所属的内存池,然后把该内存块加入到空闲内存块链表上,并增加内存池可用内存块的数目。

在释放过程中,会判断该内存池对象上是否有挂起线程,若有,则唤醒挂起线程链表上第一个线程。

第三:内存池实战演练

举个栗子。

该栗子以静态方式创建一个内存池。动态创建两个线程,一个线程试图从内存池申请内存块,一个线程释放内存块。

示例代码如下:

#include <rtthread.h>
#define THREAD_PRIORITY    25
#define THREAD_STACK_SIZE  512
#define THREAD_TIMESLICE   5
static rt_uint8_t *ptr[50];
static rt_uint8_t mempool[4096];
static struct rt_mempool mp;
/* 指向线程控制块的指针 */
static rt_thread_t tid1 = RT_NULL;
static rt_thread_t tid2 = RT_NULL;
/* 线程 1 入口 */
static void thread1_mp_alloc(void *parameter)
{
 int i;
 for (i = 0 ; i < 10 ; i++)
 {
  if (ptr[i] == RT_NULL)
  {
   /* 试图申请内存块 50 次,当申请不到内存块时,
    线程 1 挂起, 转至线程 2 运行 */
   ptr[i] = rt_mp_alloc(&mp, RT_WAITING_FOREVER);
   if (ptr[i] != RT_NULL)
   {
    rt_kprintf("allocate No.%d\n", i);
   }
  }
  rt_thread_mdelay(1);
 }
}
/* 线程 2 入口, 线程 2 的优先级比线程 1 低,应该线程 1 先获得执行。 */
static void thread2_mp_release(void *parameter)
{
 int i;
 rt_kprintf("thread2 try to release block\n");
 for (i = 0; i < 10 ; i++)
 {
  /* 释放所有分配成功的内存块 */
  if (ptr[i] != RT_NULL)
  {
   rt_kprintf("release block %d\n", i);
   rt_mp_free(ptr[i]);
   ptr[i] = RT_NULL;
  }
  rt_thread_mdelay(1);
 }
}
int main(void)
{
 int i;
 for (i = 0; i < 50; i ++) 
 {
  ptr[i] = RT_NULL;
 }
 /* 初始化内存池对象 */
 rt_mp_init(&mp, "mp1", &mempool[0], sizeof(mempool), 80);
 /* 创建线程1:申请内存池 */
 tid1 = rt_thread_create("thread1", thread1_mp_alloc, RT_NULL,
       THREAD_STACK_SIZE,
       THREAD_PRIORITY, THREAD_TIMESLICE);
 if (tid1 != RT_NULL)
 {
  rt_thread_startup(tid1);
 }
 /* 创建线程 2:释放内存池 */
 tid2 = rt_thread_create("thread2", thread2_mp_release, RT_NULL,
       THREAD_STACK_SIZE,
       THREAD_PRIORITY + 1, THREAD_TIMESLICE);
 if (tid2 != RT_NULL)
 {
  rt_thread_startup(tid2);
 }
 return 0;
}

编译运行结果如下:

 

第四:其他管理函数

上面详细介绍了 RT-Thread 内存池常用的几个接口函数。还有几个相关的函数,在这简单介绍一下,了解了解。

1. 删除动态创建的内存池

删除 rt_mp_create() 函数创建的内存池,需要调用如下函数:

rt_err_t rt_mp_delete(rt_mp_t mp)

这个函数首先唤醒等待在该内存池对象上的所有线程,然后释放掉从内存堆上申请的内存缓冲区。

2. 脱离静态创建的内存池

脱离 rt_mp_init() 函数初始化的内存池,函数接口如下:

rt_err_t rt_mp_detach(rt_mp_t mp)

调用该函数后,内核先唤醒等待在该内存池对象上的所有线程,然后将内存池对象从内核对象管理器中脱离。

第五:小结

利用两篇文章介绍完毕 RT-Thread 内存管理相关的内容:

  • 内存堆管理。内存堆方便灵活,但是容易出现碎片以及分配效率低。
  • 内存池管理。内存池分配速度快,不会产生内存碎片,但是只能申请固定大小的内存块,不够灵活。

各有优缺点,需要根据实际情况选择何种方式进行管理内存。

目录
相关文章
|
7月前
|
Dragonfly 算法 安全
RT-Thread快速入门-动态内存堆管理方法
RT-Thread快速入门-动态内存堆管理方法
113 0
|
消息中间件 存储 传感器
RT-Thread记录(八、理解 RT-Thread 内存管理)
RT-Thread内核的我们已经基本都学习过了,除了基本的线程操作和通信, 内核部分还有内存管理和中断处理,本文主要就来说说内存管理相关问题。
405 0
RT-Thread记录(八、理解 RT-Thread 内存管理)
|
29天前
|
缓存 Prometheus 监控
Elasticsearch集群JVM调优设置合适的堆内存大小
Elasticsearch集群JVM调优设置合适的堆内存大小
250 1
|
19天前
|
存储 监控 算法
深入探索Java虚拟机(JVM)的内存管理机制
本文旨在为读者提供对Java虚拟机(JVM)内存管理机制的深入理解。通过详细解析JVM的内存结构、垃圾回收算法以及性能优化策略,本文不仅揭示了Java程序高效运行背后的原理,还为开发者提供了优化应用程序性能的实用技巧。不同于常规摘要仅概述文章大意,本文摘要将简要介绍JVM内存管理的关键点,为读者提供一个清晰的学习路线图。
|
28天前
|
Java
JVM内存参数
-Xmx[]:堆空间最大内存 -Xms[]:堆空间最小内存,一般设置成跟堆空间最大内存一样的 -Xmn[]:新生代的最大内存 -xx[use 垃圾回收器名称]:指定垃圾回收器 -xss:设置单个线程栈大小 一般设堆空间为最大可用物理地址的百分之80
|
29天前
|
Java
JVM运行时数据区(内存结构)
1)虚拟机栈:每次调用方法都会在虚拟机栈中产生一个栈帧,每个栈帧中都有方法的参数、局部变量、方法出口等信息,方法执行完毕后释放栈帧 (2)本地方法栈:为native修饰的本地方法提供的空间,在HotSpot中与虚拟机合二为一 (3)程序计数器:保存指令执行的地址,方便线程切回后能继续执行代码
21 3
|
29天前
|
存储 缓存 监控
Elasticsearch集群JVM调优堆外内存
Elasticsearch集群JVM调优堆外内存
46 1
|
1月前
|
Arthas 监控 Java
JVM进阶调优系列(9)大厂面试官:内存溢出几种?能否现场演示一下?| 面试就那点事
本文介绍了JVM内存溢出(OOM)的四种类型:堆内存、栈内存、元数据区和直接内存溢出。每种类型通过示例代码演示了如何触发OOM,并分析了其原因。文章还提供了如何使用JVM命令工具(如jmap、jhat、GCeasy、Arthas等)分析和定位内存溢出问题的方法。最后,强调了合理设置JVM参数和及时回收内存的重要性。
|
2月前
|
缓存 算法 Java
JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS
这篇文章详细介绍了Java虚拟机(JVM)中的垃圾回收机制,包括垃圾的定义、垃圾回收算法、堆内存的逻辑分区、对象的内存分配和回收过程,以及不同垃圾回收器的工作原理和参数设置。
83 4
JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS
|
2月前
|
存储 算法 Java
Java虚拟机(JVM)的内存管理与性能优化
本文深入探讨了Java虚拟机(JVM)的内存管理机制,包括堆、栈、方法区等关键区域的功能与作用。通过分析垃圾回收算法和调优策略,旨在帮助开发者理解如何有效提升Java应用的性能。文章采用通俗易懂的语言,结合具体实例,使读者能够轻松掌握复杂的内存管理概念,并应用于实际开发中。