内存堆管理应用示例
这是一个内存堆的应用示例,这个程序会创建一个动态的线程,这个线程会动态申请内存并释放,每次申请更大的内存,当申请不到的时候就结束。
#include <rtthread.h>
#define THREAD_PRIORITY 25
#define THREAD_STACK_SIZE 512
#define THREAD_TIMESLICE 5
/* 线程入口 */
void thread1_entry(void *parameter)
{
int i;
char *ptr = RT_NULL; /* 内存块的指针 */
for (i = 0; ; i++)
{
/* 每次分配 (1 << i) 大小字节数的内存空间 */
ptr = rt_malloc(1 << i);
/* 如果分配成功 */
if (ptr != RT_NULL)
{
rt_kprintf("get memory :%d byte\n", (1 << i));
/* 释放内存块 */
rt_free(ptr);
rt_kprintf("free memory :%d byte\n", (1 << i));
ptr = RT_NULL;
}
else
{
rt_kprintf("try to get %d byte memory failed!\n", (1 << i));
return;
}
}
}
int dynmem_sample(void)
{
rt_thread_t tid = RT_NULL;
/* 创建线程 1 */
tid = rt_thread_create("thread1",
thread1_entry, RT_NULL,
THREAD_STACK_SIZE,
THREAD_PRIORITY,
THREAD_TIMESLICE);
if (tid != RT_NULL)
rt_thread_startup(tid);
return 0;
}
/* 导出到 msh 命令列表中 */
MSH_CMD_EXPORT(dynmem_sample, dynmem sample);
内存池
内存堆管理器可以分配任意大小的内存块,非常灵活和方便。
但其也存在明显的缺点:
- 分配效率不高,在每次分配时,都要在空闲内存块查找。
- 容易产生内存碎片。
为了提高内存分配的效率,并且避免内存碎片,RT-Thread提供了另外一种内存管理方法:内存池(Memory Pool)。
内存池是一种内存分配方式,用于分配大量大小相同的小内存块,它可以极大加快内存分配与释放的速度,且能尽量避免内存碎片化。
此外,RT-Thread的内存池支持线程挂起功能,当内存池中无空闲内存块时,申请线程会被挂起,直到内存池中有新的可用内存块,再将挂起的申请线程唤醒。
内存池的线程挂起功能非常适合需要通过内存资源进行同步的场景,例如播放音乐时,播放器线程会对音乐文件进行解码,然后发送到声卡驱动,从而驱动硬件播放音乐。
- 当播放器线程需要解码数据时,就会向内存池请求内存块,如果内存块已经用完,线程将被挂起,否则它将获得内存块以放置解码的数据。
- 播放器线程把包含解码数据的内存块写入到声卡抽象设备中(线程会立刻返回,继续解码出更多的数据)。
- 当声卡设备写入完成后,将调用播放器线程设置的回调函数,释放写入的内存块,如果在此之前,播放器线程因为把内存池里的内存块都用完而被挂起的话,那么这时它将被将唤醒,并继续进行解码。
内存池控制块
内存池控制块是操作系统用于管理内存池的一个数据结构,它会存放内存池的一些信息,例如内存块数据区域开始地址,内存块大小和内存块列表等,也包含内存块与内存块之间连接用的链表结构,因内存块不可用而挂起的线程等待事件集合等。
struct rt_mempool
{
struct rt_object parent;
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;
内存块分配机制
内存池在创建时先向系统申请一大块内存,然后分成同样大小的多个小内存块,小内存块直接通过链表连接起来(此链表也称为空闲链表)。
每次分配的时候,从空闲链表中取出链头上第一个内存块,提供给申请者。
物理内存中允许存在多个大小不同的内存池,每一个内存池又由多个空闲内存块组成,内核用它们来进行内存管理。
当一个内存池对象被创建时,内存池对象就被分配给了一个内存池控制块,内存池控制块的参数包括内存池名,内存缓冲区,内存块大小,块数以及一个线程等待队列。
内核负责给内存池分配内存池控制块,它同时也接收用户线程的分配内存块申请。
内存池一旦初始化完成,内部的内存块大小将不能再做调整。
suspend_thread形成了一个申请线程等待列表,即当内存池中无可用内存块,并且申请线程允许等待时,申请线程将挂在suspend_thread链表上。
内存池的管理方式
内存池控制块是一个结构体,其中含有内存池相关的重要参数。
创建和删除内存池
创建内存池操作将会创建一个内存池对象并从堆上分配一个内存池。
rt_mp_t rt_mp_create(const char* name,rt_size_t block_count, rt_size_t block_size);
使用该函数接口可以创建一个与需求的内存块大小、数目相匹配的内存池,前提当然是在系统资源允许的情况下(最主要的内存堆内存资源)才能创建成功。
创建内存池时,需要给内存池指定一个名称。
然后内核从系统中申请一个内存池对象,然后从内存堆中分配一块由块数目和块大小计算得来的内存缓冲区,接着初始化内存池对象,并将申请成功的内存缓冲区组织成可用于分配的空闲块链表。
删除内存池将删除内存池对象并释放申请的内存。
rt_err_t rt_mp_delete(rt_mp_t mp);
删除内存池时,会首先唤醒等待在该内存池对象上的所有线程(返回-RT_ERROR),然后再释放已从内存堆上分配的内存池数据存放区域,然后删除内存池对象。
内存池应用示例
这是一个静态内存池应用例程,创建一个静态的内存池对象,2个动态线程,1个线程试图从内存池中获得内存块,另一个线程释放内存块。
#include <rtthread.h>
static rt_uint8_t *ptr[50];
static rt_uint8_t mempool[4096];
static struct rt_mempool mp;
#define THREAD_PRIORITY 25
#define THREAD_STACK_SIZE 512
#define THREAD_TIMESLICE 5
/* 指向线程控制块的指针 */
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 < 50 ; 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);
}
}
}
/* 线程 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 < 50 ; i++)
{
/* 释放所有分配成功的内存块 */
if (ptr[i] != RT_NULL)
{
rt_kprintf("release block %d\n", i);
rt_mp_free(ptr[i]);
ptr[i] = RT_NULL;
}
}
}
int mempool_sample(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;
}
/* 导出到 msh 命令列表中 */
MSH_CMD_EXPORT(mempool_sample, mempool sample);