一个简单的内存池的实现
当频繁地用malloc申请内存,然后再用free释放内存时,会存在两个主要问题。第一个问题是频繁的分配释放内存可能导致系统内存碎片过多;第二个问题是分配释放内存花费的时间可能比较多(这个问题不太明显)。这个时候我们就可以考虑使用内存池了。
最朴素的内存池思想就是,首先你向系统申请一块很大的内存(这块内存因为很大,以致于我们常称它为memory pool),然后你在上面实现类似于malloc和free等操作。当你需要分配内存时,你用自己的类malloc函数从内存池上取一小块给使用者(后文称之为小内存块),当用类free函数释放从内存池上取得的内存时,这个小内存块也并不归还给系统,而只是还给内存池。
最近写telnet还原程序,写了个简易内存池,和大家分享下。
这个内存池提供这样的功能:i)提供类malloc函数mem_alloc从内存池上获取固定大小的内存,提供类free函数mem_free释放内存到内存池中;ii)内存池初始大小为某个固定大小(BUF_SIZE)的n倍,当内存池中的内存不够用时,内存池能以一定步长增长直到无系统内存。
要实现这些功能,首先应该用一个数据结构(mem_node_t)将内存池中的各个小内存块挂起来;然后为了使内存池能够以一定步长增长,我们要能分配多个大的内存块(即多个小内存池),为了能顺利的管理各个大的内存块,需要一个数据结构(mem_block_t)来记录这些信息;最后,内存池的当前状态(包括可分配的空闲链表,空闲的小内存块的个数等)是我们感兴趣的东西,所以用数据结构(mem_pool_t)来记录。
typedef union _mem_node
{
union _mem_node *next;
char buf[BUF_SIZE];
}mem_node_t, *pmem_node_t;
typedef struct _mem_block
{
mem_node_t *node_head; /* 第一个小内存块 */
mem_node_t *node_tail; /* 最后一个小内存块 */
int node_cnt; /* node count */
struct _mem_block *next;
}mem_block_t, *pmem_block_t;
typedef struct _mem_pool
{
mem_block_t *block_head; /* 第一个大内存块 */
mem_block_t *block_tail; /* 最后一个大内存块 */
mem_node_t *free_head; /* 当前空闲链表的头指针 */
int block_cnt; /* block count */
int free_cnt; /* free node count; */
int base; /* 内存池的初始大小 */
int step; /* 内存池的增长步长 */
}mem_pool_t, *pmem_pool_t;
然后提供了一些操作函数:mem_pool_init用来初始化内存池;mem_pool_destroy用来释放内存池,将内存池所占空间归还系统;print_mem_pool_info用来打印内存池的信息;mem_alloc,用来从内存池上分配小内存块;mem_free,将小内存块归还给内存池。
mem_alloc和mem_free操作的就是内存池的空闲链表,前者从空闲链表取一个结点,后者将一个结点插入空闲链表。而这个空闲链表是由mem_pool_init初始化的,而且当内存池增长时,即增加新的大内存块时,我们将大内存块上的小内存块也挂接到这个空闲链表上来。需要注意的是小内存块的数据结构用了联合,这是因为小内存块要么是挂接在空闲链表上,要么是分配给了用户,必居且只能居这两种状态之一,这个数据结构可根据需求适当改进。
在码代码之前,要补充说明的是,不少内存池都提供了分配不同大小的内存块的功能,将多个不同大小的本内存池链接起来也可实现这些功能,改动是比较容易的^_^
最后,本内存池在vc6下验证当频繁分配释放100字节大小的内存时,本内存池效率约是直接malloc和free的10倍,当分配大小变小时,效率比有所降低,但当分配大小增大时,效率比有所升高,分配1000字节大小的内存时,效率比约为100。
代码。
/************************mem_pool.h************************
*author:bripengandre
*
**********************************************************/
#ifndef _MEM_POOL_H_
#define _MEM_POOL_H_
#define BUF_SIZE 100
#define BASE_COUNT 10000
#define STEP_COUNT 1000
/* #pragma pack () */
/* */
typedef union _mem_node
{
union _mem_node *next;
char buf[BUF_SIZE];
}mem_node_t, *pmem_node_t;
/* used to store block information */
typedef struct _mem_block
{
mem_node_t *node_head;
mem_node_t *node_tail;
int node_cnt; /* node count */
struct _mem_block *next;
}mem_block_t, *pmem_block_t;
/* used to store the pool information */
typedef struct _mem_pool
{
mem_block_t *block_head;
mem_block_t *block_tail;
mem_node_t *free_head;
int block_cnt; /* block count */
int free_cnt; /* free node count; */
int base;
int step;
}mem_pool_t, *pmem_pool_t;
/* mem_pool will have at least base blocks, and will increase steps a time if needed */
int mem_pool_init(int base, int step);
void mem_pool_destroy(void);
void print_mem_pool_info(void);
/* since the block size is constant, this function need no input parameter */
void *mem_alloc(void);
void mem_free(void *ptr);
#endif /* _MEM_POOL_H */
/************************mem_pool.c************************
*author:bripengandre
*
**********************************************************/
#include "mem_pool.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define MEM_POOL_DEBUG
/* add new memory block to our memory pool */
static int add_mem_block(int cnt);
/* init the new block */
static int mem_block_init(int cnt, mem_block_t *block);
/* init free_list of the new block */
static int free_list_init(const mem_block_t *block);
static mem_pool_t mem_pool;
/* mem_pool will have at least base blocks, and will increase steps a time if needed */
int mem_pool_init(int base, int step)
{
if(base <= 0)
{
base = BASE_COUNT;
}
if(step <= 0)
{
step = STEP_COUNT;
}
/* initiate mem_pool */
memset(&mem_pool, 0, sizeof(mem_pool));
mem_pool.base = base;
mem_pool.step = step;
/* add the base block(node of base count) into the memory pool */
if(!add_mem_block(base))
{
fprintf(stderr, "mem_pool_init::add_mem_block error/n");
return 0;
}
return 1;
}
void mem_pool_destroy(void)
{
mem_block_t *prev, *cur;
prev = NULL;
cur = mem_pool.block_head;
while(prev != NULL)
{
prev = cur;
cur = cur->next;
free(cur->node_head);
free(prev);
}
memset(&mem_pool, 0, sizeof(mem_pool_t));
}
void print_mem_pool_info(void)
{
int i;
mem_block_t *p;
if(mem_pool.block_head == NULL)
{
fprintf(stderr, "memory pool has been created!/n");
return;
}
printf("***************memory pool information start***********************/n");
printf("block count: %4d/n", mem_pool.block_cnt);
printf("current free node count: %4d/n", mem_pool.free_cnt);
printf("base block size: %4d/n", mem_pool.base);
printf("increasing block size: %4d/n", mem_pool.step);
printf("the first block: %#x/n", mem_pool.block_head);
printf("the last block: %#x/n", mem_pool.block_tail);
printf("the first free node: %#x/n/n", mem_pool.free_head);
for(p = mem_pool.block_head, i = 0; p != NULL; p = p->next, i++)
{
printf("-------------------block %4d--------------------------/n", i+1);
printf("node count: %4d/n", p->node_cnt);
printf("the first node: %#x/n", p->node_head);
printf("the last node: %#x/n", p->node_tail);
printf("------------------------------------------------------/n");
}
printf("************************memory pool information end**************************************/n");
}
/* since the block size is constant, this function need no input parameter */
void *mem_alloc(void)
{
mem_node_t *p;
/* no free node ready, attempt to allocate new free node */
if(mem_pool.free_head == NULL)
{
if(!add_mem_block(mem_pool.step))
{
return NULL;
}
}
/* get free node from free_list */
p = mem_pool.free_head;
mem_pool.free_head = p->next;
/* decrease the free node count */
mem_pool.free_cnt--;
return p;
}
void mem_free(void *ptr)
{
if(ptr == NULL)
{
return;
}
/* return the node to free_list */
((mem_node_t *)ptr)->next = mem_pool.free_head;
mem_pool.free_head = ptr;
/* increase the free node count */
mem_pool.free_cnt++;
}
/* add new memory block to our memory pool */
static int add_mem_block(int cnt)
{
mem_block_t *block;
if( (block = malloc(sizeof(mem_block_t))) == NULL)
{
fprintf(stderr, "add_mem_block::malloc error/n");
return 0;
}
memset(block, 0, sizeof(mem_block_t));
if(!mem_block_init(cnt, block))
{
fprintf(stderr, "mem_pool_init::mem_block_init error/n");
return 0;
}
/* insert the new block in the head */
block->next = mem_pool.block_head;
mem_pool.block_head = block;
if(mem_pool.block_tail == NULL)
{
mem_pool.block_tail = block;
}
/* insert the new block into the free list */
block->node_tail->next = mem_pool.free_head;
mem_pool.free_head = block->node_head;
mem_pool.free_cnt += cnt;
/* increase the block count */
mem_pool.block_cnt++;
return 1;
}
/* init the new block */
static int mem_block_init(int cnt, mem_block_t *block)
{
int size;
mem_node_t *p;
if(block == NULL)
{
return 0;
}
size = cnt*sizeof(mem_node_t);
if( (p = malloc(size)) == NULL)
{
fprintf(stderr, "mem_pool_init::malloc error/n");
return 0;
}
memset(p, 0, size);
memset(block, 0, sizeof(mem_block_t));
block->node_cnt = cnt;
block->node_head = p;
block->node_tail = p+cnt-1;
free_list_init(block);
return 1;
}
/* init free_list of the new block */
static int free_list_init(const mem_block_t *block)
{
mem_node_t *p, *end;
if(block == NULL)
{
return 0;
}
/* start initiating free list */
end = block->node_tail; /* block_cnt > 0 */
for(p = block->node_head; p < end; p++)
{
p->next = (p+1);
}
p->next = NULL; /* end->next = NULL */
return 1;
}
#ifdef MEM_POOL_DEBUG
#define ALLOC_COUNT 10
void alloc_test(char *ptr[])
{
int i, j;
for(i = 0; i < ALLOC_COUNT; i++)
{
if( (ptr[i] = mem_alloc()) == NULL)
{
fprintf(stderr, "mem_alloc error/n");
return;
}
for(j = 0; j < ALLOC_COUNT; j++)
{
ptr[i][j] = 'a' + j;
}
}
for(i = 0; i < ALLOC_COUNT; i++)
{
for(j = 0; j < ALLOC_COUNT; j++)
{
printf("ptr[%d][%d]=%c ", i, j, ptr[i][j]);
}
fputc('/n', stdout);
}
}
int main(int argc, char *argv[])
{
int base, step;
char *ptr1[ALLOC_COUNT], *ptr2[ALLOC_COUNT];
switch(argc)
{
case 1:
base = 0; /* default count */
step = 0; /* default count */
break;
case 2:
base = atoi(argv[1]);
step = 0;
break;
case 3:
base = atoi(argv[1]);
step = atoi(argv[2]);
break;
default:
fprintf(stderr, "Usage: %s [<base> [step]]/n", argv[0]);
break;
}
if(!mem_pool_init(base, step))
{
fprintf(stderr, "mem_pool_init error/n");
return 1;
}
print_mem_pool_info();
alloc_test(ptr1);
print_mem_pool_info();
mem_free(ptr1[5]);
print_mem_pool_info();
alloc_test(ptr2);
print_mem_pool_info();
mem_pool_destroy();
/* once again */
if(!mem_pool_init(base, step))
{
fprintf(stderr, "mem_pool_init error/n");
return 1;
}
print_mem_pool_info();
alloc_test(ptr1);
print_mem_pool_info();
mem_free(ptr1[5]);
print_mem_pool_info();
alloc_test(ptr2);
print_mem_pool_info();
mem_pool_destroy();
}
#endif /* #ifdef MEM_POOL_DEBUG */