内存池
前言:
和线程池一致,减少对资源创建和销毁的系统调用(malloc free); 此外,对于一个长期运行的进程,没有算法对内存进行规划,一个内存空间由于每个对象的大小不同,出现许多内存碎片。 因此,引入内存管理组件,比较通用 jemalloc tcmalloc。实际的内存池,往往针对大文件,小文件有多个内存池。 虽然实际工作不会开发内存池,但是自己的锻炼仍有意义。
目标:
1、定长数据的内存池; 2、不定长的内存池;
内存结构
前面我们学习了标准进程下情况C/C++中存在 动态数据区、静态数据区、代码段(文本段)

其中.开头表示的区域,是在编译阶段就已经确定的;占用固定大小区域,并有编译器自动分配与释放; 动态数据区的数据是在运行过程中使用的;;具体的,动态数据区有堆和栈,堆向高地址生长,栈向低地址生长;静态数据区包括bss段、静态数据data段和只读数据段rodata,bss段是未初始化或初始为0的数据,固定大小,运行时可读可写,而data和rodata都正常初始化了,代码段存储编译后的CPU指令,固定大小,只读且保持不变。
设计思路:
定长数据的内存池:
关键在于找到每次新创建的块,应该在哪?考虑一种问题,已创建的块1,2,3;释放了2.后面再次创建时,应该创建2。 为了实现这个采用了free_ptr指向链表头部,使用头插法,将已经释放的块加入到链表中,后续空闲内存,都通过这个链表取用。
typedef struct mempool_s {
int blocksize;
int freecount;
char *free_ptr;
char *mem;
} mempool_t;
C中高明的链表构造方法
int i = 0; //这个是链表
char *ptr = m->mem;
for (i = 0;i < m->freecount;i ++) {
*(char **)ptr = ptr + block_size;
ptr = ptr + block_size;
}
*(char **)ptr = NULL;
不定长的内存池:
typedef struct mp_node_s { //小块
unsigned char *last;
unsigned char *end;
struct mp_node_s *next;
} mp_node_t;
typedef struct mp_large_s {
struct mp_large_s *next;
void *alloc;
} mp_large_t;
typedef struct mp_pool_s {
size_t max; //大小内存临界
struct mp_node_s *head;
struct mp_large_s *large;
} mp_pool_t;
解释一下思路:
对于小块(设计上大小小于指定大小认为是小块)放置到指定小块页下面,mp_node_s就是该页的头部,last表示该页的数据尾部(从这里新增数据),end表示整个页的尾部(判断剩余数据空间是否可放置新数据),next是下一个页指针。 新来的小数据都放置到last后面,如果空间不足,新建页在放置。
对于大块,其结构体mp_large_s 也当做小块内存,放置在小块页下,next表示所有大块内存组成一个链表,alloc指向了大块数据的地址。建立大内存时,如果没有节点,创造一个,如果有且alloc==NULL,选用它, 我们只实现了大块内存的释放(释放指定ptr下的alloc,并alloc=NULL)
思考:
1、小块没能实现,释放后复用,如何实现?
2、大内存的建立是尾插法 如果改为头插法呢?
代码:
#include <stddef.h>
#include <stdlib.h>
#include <stdio.h>
typedef struct mp_node_s { //小块
unsigned char *last;
unsigned char *end;
struct mp_node_s *next;
} mp_node_t;
typedef struct mp_large_s {
struct mp_large_s *next;
void *alloc;
} mp_large_t;
typedef struct mp_pool_s {
size_t max; //大小内存临界
struct mp_node_s *head;
struct mp_large_s *large;
} mp_pool_t;
int mp_create(mp_pool_t *pool, size_t size);
void mp_destory(mp_pool_t *pool);
void *mp_alloc(mp_pool_t *pool, size_t size);
void mp_free(mp_pool_t *pool, void *ptr);
// size : 4096 池
int mp_create(mp_pool_t *pool, size_t size) {
if (!pool || size <= 0) return -1;
void *mem = malloc(size);
struct mp_node_s *node = (struct mp_node_s *)mem;
node->last = (char *)mem + sizeof(struct mp_node_s);
node->end = (char *)mem + size;
node->next = NULL;
pool->head = node;
pool->max = size;
pool->large = NULL;
return 0;
}
void mp_destory(mp_pool_t *pool) {
mp_large_t *l;
for (l = pool->large;l;l = l->next) {
if (l->alloc) {
free(l->alloc);
}
}
mp_node_t *node = pool->head;
while (node) {
mp_node_t *tmp = node->next;
free(node);
node = tmp;
}
pool->head = NULL;
pool->large = NULL;
}
//
static void *mp_alloc_block(mp_pool_t *pool, size_t size) {
// 分配一个新块,大小与初始块相同(pool->max)
char *mem = (char *)malloc(pool->max);
if (!mem) return NULL;
struct mp_node_s *node = (struct mp_node_s *)mem;
node->last = (char *)mem + sizeof(struct mp_node_s);
node->end = (char *)mem + pool->max;
node->next = NULL;
// tail insert --> head insert
mp_node_t *iter = pool->head; // 尾插到小内存块链表
while (iter->next != NULL) {
iter = iter->next;
}
iter->next = node;
// 从新节点中分配 size 字节
void *ptr = node->last;
node->last += size;
return ptr;
}
//
static void *mp_alloc_large(mp_pool_t *pool, size_t size) {
if (!pool) return NULL;
void *ptr = malloc(size);
if (ptr == NULL) return NULL;
mp_large_t *l;
for (l = pool->large;l;l = l->next) {
if (l->alloc == NULL) {
l->alloc = ptr;
return ptr;
}
}
l = mp_alloc(pool, sizeof(mp_large_t));
if (l == NULL) {
free(ptr);
return NULL;
}
l->alloc = ptr;
l->next = pool->large;//将新节点插入到大块链表的头部
pool->large = l;
return ptr;
}
void *mp_alloc(mp_pool_t *pool, size_t size) {
if (size > pool->max) {
// malloc_large;
return mp_alloc_large(pool, size);
}
// size <= pool->max
void *ptr = NULL;
mp_node_t *node = pool->head;
do {
if (node->end - node->last > size) {
ptr = node->last;
node->last += size;
return ptr;
} //找到尾部节点的尾部
node = node->next;
} while (node);
//需要新建新节点
return mp_alloc_block(pool, size);
}
//
void mp_free(mp_pool_t *pool, void *ptr) {//只释放大块
mp_large_t *l;
for (l = pool->large;l;l = l->next) {
if (l->alloc == ptr) {
free(l->alloc);
l->alloc = NULL;
return ;
}
}
//
}
/* ========== 测试主函数 ========== */
int main() {
mp_pool_t pool;
int ret = mp_create(&pool, 1024); // 初始块大小 1024 字节
if (ret != 0) {
printf("create failed: %d\n", ret);
return 1;
}
printf("Pool created, max=%zu\n", pool.max);
// 1. 分配几个小块(小于 1024)
void *s1 = mp_alloc(&pool, 200);
void *s2 = mp_alloc(&pool, 300);
printf("Small alloc: s1=%p, s2=%p\n", s1, s2);
// 2. 分配一个大块(大于 1024)
void *big1 = mp_alloc(&pool, 1500);
printf("Large alloc #1: %p\n", big1);
// 3. 再分配一个大块,此时 large 链表只有一个节点(big1),且没有空闲节点,会创建新 large 节点
void *big2 = mp_alloc(&pool, 1200);
printf("Large alloc #2: %p\n", big2);
// 4. 释放 big1
mp_free(&pool, big1);
printf("Freed big1\n");
// 5. 再次分配一个大块,应复用 big1 对应的 large 节点(alloc 被置 NULL 后重新赋值)
void *big3 = mp_alloc(&pool, 800);
printf("Large alloc #3 (should reuse node): %p\n", big3);
// 6. 检查 large 链表状态(遍历打印)
printf("\nLarge list:\n");
mp_large_t *l = pool.large;
while (l) {
printf(" node=%p, alloc=%p\n", (void*)l, l->alloc);
l = l->next;
}
// 7. 释放所有资源
mp_destory(&pool);
printf("Pool destroyed.\n");
printf("Pool over.\n");
return 0;
}

浙公网安备 33010602011771号