内存池简介
之前讲的动态内存堆可以分配任意大小的内存块,非常灵活和方便。但其存在明显的缺点:
- 分配效率不高,在每次分配时都要进行空闲内存块查找;
- 容易产生碎片。
为了提高内存分配效率,并且避免内存碎片,RT-Thread 提供了另外一种内存管理方法:内存池(Memory Pool)
内存池是一种内存分配方式,用于分配大量大小相同的小内存块。使用内存池可以极大地加快内存分配与释放的速度,并且能尽量避免内存碎片化。
RT-Thread 的内存池支持线程挂起功能,当内存池中无空闲内存块时,申请线程会被挂起,直到内存池中有新的可用内存块,再将挂起的线程唤醒。基于这个特点内存池非常适合需要通过内存资源进行同步的场景。
内存池工作机制
内存池在创建时首先从系统中获取一大块内存(静态或动态),然后分成相同大小的多个小内存块,这些小内存块通过链表连接起来(此链表也称为空闲链表)。
线程每次申请分配内存块的时候,系统从空闲链表中取出链头上第一个内存块,提供给申请者。
内存池控制块
在RT-Thread 中,内存池控制块是操作系统用于管理内存池的一个数据结构。
struct rt_mempool
{
struct rt_object parent; /**< inherit from rt_object */
void *start_address; /**< memory pool start */
rt_size_t size; /**< size of memory pool */
rt_size_t block_size; /**< size of memory blocks */
rt_uint8_t *block_list; /**< memory blocks list */
rt_size_t block_total_count; /**< numbers of memory block */
rt_size_t block_free_count; /**< numbers of free memory block */
rt_list_t suspend_thread; /**< threads pended on this resource */
rt_size_t suspend_thread_count; /**< numbers of thread pended on this resource */
};
typedef struct rt_mempool *rt_mp_t;
| 成员 | 说明 |
|---|---|
| parent | 继承自系统rt_object 对象 |
| start_address | 内存池起始地址 |
| size | 内存池大小 |
| block_size | 内存块大小 |
| block_list | 内存块列表 |
| block_total_count | 内存块数量 |
| block_free_count | 空闲内存块数量 |
| suspend_thread | 挂起的线程的列表 |
| suspend_thread_count | 挂起的线程的数量 |
定义静态内存池
struct rt_mempool static_mp;
定义动态内存池
(指针型定义)
rt_mp_t dynamic_mp;
内存池的操作
初始化与脱离
rt_err_t rt_mp_init(struct rt_mempool *mp,
const char *name,
void *start,
rt_size_t size,
rt_size_t block_size);
rt_err_t rt_mp_detach(struct rt_mempool *mp);
rt_mp_init
| 参数 | 说明 |
|---|---|
| mp | 内存池控制块地址 |
| name | 内存池名称 |
| start | 系统中的一个地址 |
| size | 内存池大小 |
| block_size | 内存块大小 |
block_size 和消息队列中msg_size 一样,都需要为系统设置的对齐方式的整数倍
内存块计算:内存块数量 = 内存池大小 / [内存块大小(符合对齐方式) + 32位指针]
通过rt_mp_init 和rt_mp_detach 可以将内存池添加进系统管理器中或将其从系统管理器中脱离。
创建与删除
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_mp_create
若系统当前有可用的内存,即可创建动态内存池。
| 参数 | 说明 |
|---|---|
| name | 内存池名称 |
| block_count | 内存块数量 |
| block_size | 内存块大小 |
通过rt_mp_create 和rt_mp_delete 可以将内存池添加进系统管理器中或将其从系统管理器中移除。
申请内存块
void *rt_mp_alloc(rt_mp_t mp, rt_int32_t time);
| 参数 | 说明 |
|---|---|
| mp | 指定需要申请内存块的内存池 |
| time | 等待的时间 |
释放内存块
void rt_mp_free(void *block);
| 参数 | 说明 |
|---|---|
| block | 要释放的内存块的地址 |
示例代码
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); /* 4096 / (80+4) = 48 */
/* 创建线程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);
实验现象
由于内存池中最多只有48 块内存块,故当线程1 申请48次后停止挂起;线程2 此时开始释放内存池,释放了一块后线程1 又开始取走一块,接着又挂起,线程2 又释放一块,线程1 又取走,现在就以及取完了50 块内存块,线程1 结束。线程2 接着又释放完48块,共计释放了50 块。