文章目录
一、不定长内存池设计
1. 整体设计
- 先创建一个池(
mp_create_pool
)。 - 分配内存时,根据 请求大小,走不同的逻辑:
- 小块内存(≤pool->max):从池子里的节点(block)中划分。
- 大块内存(>pool->max):直接
malloc
,单独管理。
2. 小块内存的分配逻辑
- 内存池内部维护一个节点链表(node list)。
- 每个节点是一个大块内存,里面可以分配多个小块。
- 分配步骤:
- 遍历节点链表,在当前节点的剩余空间中找到一块足够大的内存。。
- 如果有,就直接从
last
指针处分配一块。 - 如果没有,调用
mp_alloc_block
申请一个新节点加入链表。 - 返回分配到的小块地址。
特点:小块内存一般不单独释放,只能等整个池
reset
或destroy
。
3. 大块内存的分配逻辑
- 大于阈值(如 4096)的分配,直接调用
malloc
。 - 用一个
mp_large_s
链表记录这些大块。 - 可以单独释放(
mp_free
)。
4. 释放 & 重置
- 小块内存:只能整体
reset
(相当于把last
指针回退,所有小块一次性“回收”)。 - 大块内存:可以单独释放。
- 销毁池:释放所有节点和大块,整个池回收。
二、关键函数
- 小块(
size <= pool->max
):从池里事先分配好的“节点(node)”里按需bump
(推进)分配,分配很快但不能单独释放,只能重置整个池。 - 大块(
size > pool->max
):直接用malloc
分配,并把这些大块记录在pool->large
链表里,可以单独释放。
2.1 数据结构
struct mp_large_s {
struct mp_large_s *next;
// 下一个大块
void *alloc;
// malloc 得到的大块地址
};
struct mp_node_s {
unsigned char *last;
// 当前分配到哪里了
unsigned char *end;
// 节点内存结束位置
struct mp_node_s *next;
// 下一个节点
size_t failed;
// 分配失败次数
};
struct mp_pool_s {
size_t max;
// 小块的最大分配阈值
struct mp_node_s *current;
// 当前使用的节点
struct mp_large_s *large;
// 大块链表
struct mp_node_s head[0];
// 第一个节点(变长结构体)
};
mp_node_s
表示“池里的一段大内存”(一个 node 包含很多小块可以分配)。last
指示“我们已经分配到哪里了”,每次分配之后last
往前推。end
是边界,不可越过。
mp_large_s
:记录单独分配的大块。mp_pool_s
:整个内存池,管理小块节点 + 大块链表。head[0]
是 C 的变长数组技巧,意味着第一个mp_node_s
被放到 pool 内存块里,节省了一次 malloc。
2.2 创建内存池
struct mp_pool_s *mp_create_pool(size_t size) {
posix_memalign(&p, MP_ALIGNMENT, size + sizeof(struct mp_pool_s) + sizeof(struct mp_node_s));
p->max = (size < MP_MAX_ALLOC_FROM_POOL) ? size : MP_MAX_ALLOC_FROM_POOL;
p->current = p->head;
p->large = NULL;
p->head->last = (unsigned char *)p + sizeof(struct mp_pool_s) + sizeof(struct mp_node_s);
p->head->end = p->head->last + size;
return p;
}
- 用
posix_memalign
分配一大块内存(对齐)。 - 前面放
mp_pool_s
和mp_node_s
(管理信息)。 - 剩余部分作为第一个节点的分配区域。
- 设置
last
和end
。
示意(地址从低到高):
| mp_pool_s | mp_node_s (head) | node payload (size bytes) |
p->head->last
指向 node payload 的起始位置(第一个可分配字节);p->head->end
指向 node payload 的结束位置(起始 + size)。
2.3 对齐(alignment):mp_align / mp_align_ptr
- CPU 对齐会影响性能,有些平台甚至要求对齐。这里
MP_ALIGNMENT
= 32。 mp_align(n, alignment)
:把整数n
向上对齐到alignment
的倍数。mp_align_ptr(p, alignment)
:把指针p
向上对齐到alignment
的地址。
在分配时总把要返回的地址对齐,避免未对齐访问。
2.4 分配(mp_alloc)流程
(1)小块分配(size <= pool->max
)
p = pool->current;
do {
m = mp_align_ptr(p->last, MP_ALIGNMENT);
if ((size_t)(p->end - m) >= size) {
p->last = m + size;
return m;
}
p = p->next;
} while (p);
return mp_alloc_block(pool, size);
- 为什么从
pool->current
开始?current
指向一个“最有可能有空余”的 node,这样通常能减少从头遍历链表的开销(优化)。 - 对齐:
m = mp_align_ptr(p->last, MP_ALIGNMENT)
把当前last
(下一可分配地址)对齐。对齐会牺牲极少字节作为填充,但保证返回地址满足对齐要求。 - 检查剩余空间:
(p->end - m)
计算该 node 从m
到end
还有多少字节。如果≥size
,说明该 node 能满足这次请求。 - 分配并推进
last
:p->last = m + size
—— 这把last
推到分配区之后,下次分配会从新的位置继续。return m
把这次分配的起始地址返回给调用者。 - 遍历下一 node:
如果当前 node 空间不够,就试p = p->next
。 - 所有 node 都不够:
跑完链表没有合适 node 时,调用mp_alloc_block(pool, size)
申请新的 node。 - mp_alloc_block
- 申请一整块新的 node(和 head node 一样的大小)
- 把这新 node 接到 node 链表末尾,然后在这个新 node 里立刻分配所需 size 并返回其起始地址。
(2)大块分配(size > pool->max
)——mp_alloc_large
static void *mp_alloc_large(struct mp_pool_s *pool, size_t size) {
void *p = malloc(size);
if (p == NULL) return NULL;
// 查找是否有可复用的 large 节点
size_t n = 0;
struct mp_large_s *large;
for (large = pool->large; large; large = large->next) {
if (large->alloc == NULL) {
large->alloc = p;
return p;
}
if (n++ >
3) break;
}
// 如果没有可复用的,分配一个新的 large 节点
large = mp_alloc(pool, sizeof(struct mp_large_s));
if (large == NULL) {
free(p);
return NULL;
}
large->alloc = p;
large->next = pool->large;
pool->large = large;
return p;
}
先
malloc(size)
得到原始内存p
。遍历
pool->large
链(记录大块的结构),尝试找到alloc == NULL
的节点来重用该记录(即之前释放的大块的记录)。- 最多查找 3 个节点(
if (n++ > 3) break;
),这是为了避免遍历过长的链表。
- 最多查找 3 个节点(
如果没有可复用的
mp_large_s
节点,便用mp_alloc(pool, sizeof(mp_large_s))
在 pool 中分配一个mp_large_s
结构(这本身调用mp_alloc
,有可能走小块路径或失败)。- 若分配
mp_large_s
失败(pool 内已无可用空间),则free(p)
并返回NULL
。
- 若分配
否则,把
p
挂到新mp_large_s
结构上并插入链头pool->large = large
,返回p
给调用者。
要点:
mp_alloc_large
保证大块可以单独释放,因为mp_free
会查找并free
它们。- 同时实现了 大块记录复用,避免
mp_large_s
结构无限增长。
2.5 释放与回收策略
mp_free
只遍历
pool->large
链表,查找l->alloc == p
并free(l->alloc)
,然后设为NULL
(保留mp_large_s
节点以便复用)。不处理小块 —— 因为小块没有单独的元数据(每个小块没有 header 指示大小或归属 node),单独释放会复杂且容易引入碎片或许多额外元数据。
mp_reset_pool
把
pool->large
的所有alloc
都free()
并把pool->large = NULL
。对每个 node 把
h->last = (unsigned char *)h + sizeof(struct mp_node_s);
—— 把小块区域视为“全新未分配”,即一次性回收所有小块。
mp_destory_pool
释放
pool->large
的实际内存;释放
head->next
开始的所有动态创建的 node(free(h)
),注意首个 node 是内联在 pool 内存里的(因此最后free(pool)
会释放它);最后
free(pool)
本身(即创建 pool 时 posix_memalign 分配的内存)。
三、完整代码
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
// ========================== 常量与对齐宏定义 =============================
// 内存对齐粒度:32 字节对齐
#define MP_ALIGNMENT 32
// 默认页大小:4KB
#define MP_PAGE_SIZE 4096
// 从内存池中能分配的最大块(小于一页)
#define MP_MAX_ALLOC_FROM_POOL (MP_PAGE_SIZE-1)
// 将 n 向上对齐到 alignment 的整数倍
#define mp_align(n, alignment) (((n)+(alignment-1)) &
~(alignment-1))
// 将指针 p 向上对齐到 alignment 的整数倍地址
#define mp_align_ptr(p, alignment) (void *)((((size_t)p)+(alignment-1)) &
~(alignment-1))
// ========================== 结构体定义 =============================
// 管理“大块内存”的链表节点
// (当分配的内存超过阈值时,会直接 malloc,然后挂在这里)
struct mp_large_s {
struct mp_large_s *next;
// 指向下一个大块
void *alloc;
// 指向大块分配的内存地址
};
// 内存池中的“小块分配节点”
// 一个 node 表示一段连续的内存区域
struct mp_node_s {
unsigned char *last;
// 当前已分配到的位置
unsigned char *end;
// 该内存块的结尾
struct mp_node_s *next;
// 指向下一个 node
size_t failed;
// 分配失败次数(用于优化 current 指针)
};
// 内存池的主体结构
struct mp_pool_s {
size_t max;
// 小块分配的最大值(超过则走大块分配)
struct mp_node_s *current;
// 当前活跃的 node
struct mp_large_s *large;
// 大块链表
struct mp_node_s head[0];
// 内存池头节点(柔性数组)
};
// ========================== 函数声明 =============================
struct mp_pool_s *mp_create_pool(size_t size);
void mp_destory_pool(struct mp_pool_s *pool);
void *mp_alloc(struct mp_pool_s *pool, size_t size);
void *mp_nalloc(struct mp_pool_s *pool, size_t size);
void *mp_calloc(struct mp_pool_s *pool, size_t size);
void mp_free(struct mp_pool_s *pool, void *p);
// ========================== 内存池创建与销毁 =============================
// 创建内存池
struct mp_pool_s *mp_create_pool(size_t size) {
struct mp_pool_s *p;
// 分配一块对齐的内存,包含 pool 本身、node 头和 size 大小的空间
int ret = posix_memalign((void **)&p, MP_ALIGNMENT, size + sizeof(struct mp_pool_s) + sizeof(struct mp_node_s));
if (ret) {
return NULL;
}
// 设置最大可分配的小块
p->max = (size < MP_MAX_ALLOC_FROM_POOL) ? size : MP_MAX_ALLOC_FROM_POOL;
p->current = p->head;
// 当前节点指向头节点
p->large = NULL;
// 大块链表初始化为空
// 初始化头节点的可用内存范围
p->head->last = (unsigned char *)p + sizeof(struct mp_pool_s) + sizeof(struct mp_node_s);
p->head->end = p->head->last + size;
p->head->failed = 0;
return p;
}
// 销毁内存池,释放所有资源
void mp_destory_pool(struct mp_pool_s *pool) {
struct mp_node_s *h, *n;
struct mp_large_s *l;
// 释放大块内存
for (l = pool->large; l; l = l->next) {
if (l->alloc) {
free(l->alloc);
}
}
// 释放所有 node
h = pool->head->next;
while (h) {
n = h->next;
free(h);
h = n;
}
// 释放内存池本身
free(pool);
}
// 重置内存池:释放所有大块内存,清空小块分配状态
void mp_reset_pool(struct mp_pool_s *pool) {
struct mp_node_s *h;
struct mp_large_s *l;
// 释放大块
for (l = pool->large; l; l = l->next) {
if (l->alloc) {
free(l->alloc);
}
}
pool->large = NULL;
// 重置所有 node 的 last 指针,相当于“清空已分配的小块”
for (h = pool->head; h; h = h->next) {
h->last = (unsigned char *)h + sizeof(struct mp_node_s);
}
}
// ========================== 内存分配实现 =============================
// 内部函数:申请一个新的小块 node
static void *mp_alloc_block(struct mp_pool_s *pool, size_t size) {
unsigned char *m;
struct mp_node_s *h = pool->head;
size_t psize = (size_t)(h->end - (unsigned char *)h);
// 分配对齐的内存块
int ret = posix_memalign((void **)&m, MP_ALIGNMENT, psize);
if (ret) return NULL;
// 初始化新 node
struct mp_node_s *p, *new_node, *current;
new_node = (struct mp_node_s*)m;
new_node->end = m + psize;
new_node->next = NULL;
new_node->failed = 0;
// 设置 last 指针(预留 node 头结构体)
m += sizeof(struct mp_node_s);
m = mp_align_ptr(m, MP_ALIGNMENT);
new_node->last = m + size;
// 将新 node 挂到链表末尾,同时更新 current
current = pool->current;
for (p = current; p->next; p = p->next) {
if (p->failed++ >
4) {
current = p->next;
}
}
p->next = new_node;
pool->current = current ? current : new_node;
return m;
}
// 内部函数:申请大块内存
static void *mp_alloc_large(struct mp_pool_s *pool, size_t size) {
void *p = malloc(size);
if (p == NULL) return NULL;
// 查找是否有可复用的 large 节点
size_t n = 0;
struct mp_large_s *large;
for (large = pool->large; large; large = large->next) {
if (large->alloc == NULL) {
large->alloc = p;
return p;
}
if (n++ >
3) break;
}
// 如果没有可复用的,分配一个新的 large 节点
large = mp_alloc(pool, sizeof(struct mp_large_s));
if (large == NULL) {
free(p);
return NULL;
}
large->alloc = p;
large->next = pool->large;
pool->large = large;
return p;
}
// 分配对齐的小块/大块内存
void *mp_alloc(struct mp_pool_s *pool, size_t size) {
unsigned char *m;
struct mp_node_s *p;
// 小块分配
if (size <= pool->max) {
p = pool->current;
do {
m = mp_align_ptr(p->last, MP_ALIGNMENT);
if ((size_t)(p->end - m) >= size) {
p->last = m + size;
return m;
}
p = p->next;
} while (p);
return mp_alloc_block(pool, size);
}
// 大块分配
return mp_alloc_large(pool, size);
}
// 分配(不做对齐处理)
void *mp_nalloc(struct mp_pool_s *pool, size_t size) {
unsigned char *m;
struct mp_node_s *p;
if (size <= pool->max) {
p = pool->current;
do {
m = p->last;
if ((size_t)(p->end - m) >= size) {
p->last = m + size;
return m;
}
p = p->next;
} while (p);
return mp_alloc_block(pool, size);
}
return mp_alloc_large(pool, size);
}
// 分配并清零(calloc)
void *mp_calloc(struct mp_pool_s *pool, size_t size) {
void *p = mp_alloc(pool, size);
if (p) {
memset(p, 0, size);
}
return p;
}
// 释放(仅释放大块内存,小块只能在 reset 时统一回收)
void mp_free(struct mp_pool_s *pool, void *p) {
struct mp_large_s *l;
for (l = pool->large; l; l = l->next) {
if (p == l->alloc) {
free(l->alloc);
l->alloc = NULL;
return;
}
}
}
// ========================== 测试用例 =============================
int main(int argc, char *argv[]) {
int size = 1 <<
12;
// 4096
// 创建内存池
struct mp_pool_s *p = mp_create_pool(size);
// 测试小块分配
int i = 0;
for (i = 0; i <
10; i++) {
void *mp = mp_alloc(p, 512);
// mp_free(mp); // 小块不能单独 free
}
// 测试对齐宏
printf("mp_align(24, 32): %d, mp_align(17, 32): %d\n", mp_align(24, 32), mp_align(17, 32));
// 测试 calloc
int j = 0;
for (i = 0; i <
5; i++) {
char *pp = mp_calloc(p, 32);
for (j = 0; j <
32; j++) {
if (pp[j]) {
printf("calloc wrong\n");
}
printf("calloc success\n");
}
}
// 测试大块分配 + 释放
for (i = 0; i <
5; i++) {
void *l = mp_alloc(p, 8192);
mp_free(p, l);
}
// 重置内存池
mp_reset_pool(p);
// 再次分配
for (i = 0; i <
58; i++) {
mp_alloc(p, 256);
}
// 销毁内存池
mp_destory_pool(p);
return 0;
}