背景与介绍
优势
方案
先提前申请特定大小的内存,当需要使用时再进行分配,不再使用时先不释放而是统一释放。
设计与实现
组成
- 小块内存block
- 大块内存large
-
API
创建内存池mp_init
- 销毁内存池mp_destory
- 重置内存池mp_reset
-
实现
小块内存
mp_block_t
typedef struct mp_block_s {
struct mp_block_s *next;
unsigned char *last; // 指向未分配内存起始地址
unsigned char *end; // 指向最大分配地址
size_t failed;
}mp_block_t;
使用链表连接
该小块可分配内存大小:
block->end - block->last
大块内存
mp_large_t
typedef struct mp_large_s {
struct mp_large_s *next;
void *alloc;
} mp_large_t;
注意:
链表连接,即所有大块内存使用链表遍历
- 在小块内存中存储大块内存指针
- 仅在内存池首个节点块关联大块内存链表
-
内存池
mp_pool_t
typedef struct mp_pool_s {
mp_block_t *block;
mp_large_t *large;
size_t max; // 小块内存的最大分配大小
// 其他,如日志等,可对比Nginx内存池实现
mp_block_t head[0]; // 柔性数组
}mp_pool_t;
head[],柔性数组,来连接第一个小块内存
-
创建内存池
mmpool_init
mp_pool_t* mmpool_init(size_t size) {
mp_pool_t *p;
if (posix_memalign(&p, MP_ALIGNMENT, size + sizeof(mp_pool_t) + sizeof(mp_block_t)) != 0) {
return NULL;
}
p->max = (size < MP_MAX_ALLOC_FROM_POOL) ? size : MP_MAX_ALLOC_FROM_POOL;
p->block = p->head; // 指向第一个小块内存,等价p->block = p->head->last = (unsigned char *)p + sizeof(mp_pool_t) + sizeof(mp_block_t)
p->large = NULL;
p->head->last = (unsigned char *)p + sizeof(mp_pool_t) + sizeof(mp_block_t);
p->head->end = p->head->last + size;
p->head->failed = 0;
return p;
}
不要设计成
int mmpool_init(mp_pool_t *p, size_t size)
,需要在函数内部申请内存后再返回指针(堆空间)- 使用
posix_memalign
而不是malloc
:
流程描述:
- 申请内存,大小为池结构+小块内存结构+实际分配:
size + sizeof(mp_pool_t) + sizeof(mp_block_t)
- 判断size是否超出一页4k,两中取小
- 对所有结构进行初始化
疑问
- MP_MAX_ALLOC_FROM_POOL = 4k - 1,为什么要减1?
参数中的size推荐为整个池的大小还是仅为首块内存池的大小更好?
销毁内存池
mmpool_destory
// 销毁内存池
void mmpool_destory(mp_pool_t *pool) {
mp_block_s *head, *block;
mp_large_t *large;
// 1. 释放大块内存
for (large = pool->large; large; large = large->next) {
if (large->alloc) {
free(large->alloc);
}
}
// 2. 释放小块内存, 从第二块开始(第一个与pool连接在一起)
head = pool->head->next;
while (head) {
block = head->next;
free(head);
head = block;
}
// 3. 释放池结构(含首个小块内存、小块内存结构)
free(pool);
}
流程:
释放大块内存
- 释放小块内存, 从第二块开始(第一个与pool连接在一起)
-
重置内存池
void mmpool_reset(mp_pool_t *pool) {
mp_block_s *block;
mp_large_s *large;
// 1. 释放所有大块内存
for (large = pool->large; large; large = large->next) {
if (large->alloc) {
free(large->alloc);
}
}
pool->large = NULL;
// 2. 收回所有小块内存空间但不释放(即已申请的内存空间均可重新分配)
for (block = pool->head; block; block = block->next) {
block->last = (unsigned char *)block + sizeof(mp_block_t);
}
}
流程:
释放所有大块内存
收回所有小块内存空间但不释放(即已申请的内存空间均可重新分配)
分配内存
流程
比较申请大小与内存池最大来决定在小块还是大块上申请内存
- size <= pool->max时,在小块内存节点上申请
mp_alloc_block()
:- 遍历小块内存节点链表,是否可在已有小块节点上分配内存
size < block->end - block->last
,可以则分配后直接返回 - 不可以则需要在内存池上分配新的小块内存节点,并进行分配
- 遍历小块内存节点链表,是否可在已有小块节点上分配内存
- size > pool->max时,在大块内存节点上申请
mp_alloc_large()
: