最近学习了Nginx的内存池实现。想着总结记录一下。
首先为什么我们需要使用内存池,就是因为传统方式有很多弊端:
我们知道Linux内存模型中有用户态和内核态之分。而让系统分配内存需要执行系统调用,而要执行系统调用就要产生中断,就势必会有用户态到内核态的切换。而这个切换又是很耗时间,如果频繁的执行就会降低程序的效率。由于我们分配内存时都是需要多少分配多少,如果在某个循环中分配内存就会造成从用户态到内核态的频繁切换。这是传统方式的弊端之一,如果追求高效率,这种方法显然不行。
产生根源:
1.内存分配必须起始于可被 4、8 或 16 整除(视处理器体系结构而定)的地址
2.MMU的分页机制的限制
由于使用malloc函数分配内存时,系统并不会分配连续的内存。也就是说,如果我们分配了一块内存,它的起始地址是0X123456,结束地址是0X123480,分配下一块内存时,下一块内存的地址并不是从0X123480开始的。这中间有间隔。也就是内存碎片。显然,这会降低内存的利用率。这是传统方式的又一弊端。
在C语言中我们使用malloc分配内存,用free释放内存。malloc的内存一定要free掉,不然就会造成内存泄露。但在实际情况下我们可能会忘记释放或未释放完全。这就容易导致内存枯竭。
要系统分配内存需要调用 Malloc 函数,但前面提到过通过 Malloc 函数分配内存会存在很多问题。那么有没有其它可以替代 Malloc 函数的函数呢。答案是肯定的。看下图:
其中 PtMalloc 即是 Linux 默认使用的内存分配函数,简称 Malloc 。而 TcMalloc 与 JeMalloc 要使用则需要链接动态库。普通情况下使用 Malloc 函数没问题。但在多线程高效内存分配下是 TcMalloc 与 JeMalloc 效率更高。
也就是我们今天的主角内存池。可根据应用自己定制。
摘自百度百科:
内存池则是在真正使用内存之前,先申请分配一定数量的、大小相等(一般情况下)的内存块留作备用。当有新的内存需求时,就从内存池中分出一部分内存块,若内存块不够再继续申请新的内存。这样做的一个显著优点是,使得内存分配效率得到提升。
在内核中有不少地方内存分配不允许失败. 作为一个在这些情况下确保分配的方式, 内核开发者创建了一个已知为内存池(或者是 “mempool” )的抽象. 一个内存池真实地只是一类后备缓存, 它尽力一直保持一个空闲内存列表给紧急时使用.
池化技术一向如此,先提供足量的资源放在池中,当某个对象需要时,就从池中取得资源并使用。
而使用内存池最大的优点就是避免频繁的系统调用,提高效率,同时减少内存碎片的产生。
1.每个请求或者连接都会建立相应的内存池,建立好内存池之后,我们可以直接从内存池中申请所需要的内存,不用去管内存的释放,当内存池使用完成之后一次性销毁内存池。
2.区分大小内存块的申请和释放,大于池尺寸的定义为大内存块,使用单独的大内存块链表保存,即时分配和释放;小于等于池尺寸的定义为小内存块,直接从预先分配的内存块中提取,不够就扩充池中的内存,在生命周期内对小块内存不做释放,直到最后统一销毁。
1.大文件链表节点
struct ngx_pool_large_s {
ngx_pool_large_t *next; // 指向下一块大内存块的指针
void *alloc; // 大内存块的起始地址,如果为零,则表示之前挂载在这个节点上的大内存块已被释放,则该节点是可重用的链表节点
};
2.小内存块节点
typedef struct {
char *last; // 保存当前数据块中内存分配指针的当前位置。每次Nginx程序从内存池中申请内存时,
//从该指针保存的位置开始划分出请求的内存大小,并更新该指针到新的位置。
char *end; // 保存内存块的结束位置
ngx_pool_t *next; // 内存池由多块内存块组成,指向下一个数据块的位置。
ngx_uint_t failed; // 当前数据块内存不足引起分配失败的次数
} ngx_pool_data_t;
3.内存池结构体
通过 ngx_pool_t 结构体持有内存池。
typedef struct ngx_pool_s {
ngx_pool_data_t d; // 内存池当前的数据区指针的结构体
size_t max; // 当前数据块最大可分配的内存大小(Bytes)
ngx_pool_t *current; // 当前正在使用的数据块的指针
ngx_pool_large_t *large; // pool 中指向大数据块的指针(大数据快是指 size > max 的数据块)
}ngx_pool_t;
#include
#include
#include
#include
#include
#include
#include
#include
#define MAX_FAILS 5
#define MAX_COUNT 15
#define MAX_MEM 9096
typedef struct ngx_pool_large_s ngx_pool_large_t;
struct ngx_pool_large_s{
ngx_pool_large_t* next;
void* alloc;
};
typedef struct ngx_pool_s ngx_pool_t;
typedef struct{
char* last;
char* end;
ngx_pool_t* next;
int fails;
}ngx_pool_data_t;
struct ngx_pool_s{
ngx_pool_data_t d;
int max;
ngx_pool_t* current;
ngx_pool_large_t* large;
};
ngx_pool_t* ngx_create_pool(int size);
void* ngx_malloc(ngx_pool_t* pool,int size);
static void* ngx_malloc_small(ngx_pool_t* pool,int size);
static void* ngx_malloc_block(ngx_pool_t* pool,int size);
static void* ngx_malloc_large(ngx_pool_t* pool,int size);
void ngx_destroy_pool(ngx_pool_t* pool);
~
#include "mem_pool.h"
int check(void* pointer){ //检测是否是空指针
if(!pointer){
printf("pointer is NULL!\n");
return 1;
}
return 0;
}
ngx_pool_t* ngx_create_pool(int size){ //初始化内存池
printf("----------------------ngx_create_pool--------------------\n");
ngx_pool_t* pool;
pool=(ngx_pool_t*)malloc(size); //为内存池分配内存
pool->d.last=pool+sizeof(ngx_pool_t); //指向可用内存首地址
pool->d.end=pool+size; //指向分配的可用内存的末尾
pool->d.next=NULL;
pool->d.fails=0;
size-=sizeof(ngx_pool_t); //可用内存为初始分配的减去结构体占用的
pool->max=size>MAX_MEM?MAX_MEM:size;
pool->current=pool; //指向内存池现在正在使用的内存块
pool->large=NULL;
return pool;
}
void* ngx_malloc(ngx_pool_t* pool,int size){ //分配内存,如果能在小内存块里分配,这直接分配,不就创建大内存块
//printf("----------------------ngx_malloc--------------------\n");
if(check(pool)) exit(-1);
if(size<=pool->max) return ngx_malloc_small(pool,size);
else return ngx_malloc_large(pool,size);
return;
}
static void* ngx_malloc_small(ngx_pool_t* pool,int size){ //从小内存块里分配内存
//printf("----------------------ngx_malloc_small--------------------\n");
if(check(pool)) exit(-1);
ngx_pool_t* tmp=pool->current;
while(tmp){
if(tmp->d.end-tmp->d.last>=size){
char* ret=tmp->d.last;
tmp->d.last+=size;
return ret;
}
tmp=tmp->d.next;
}
return ngx_malloc_block(pool,size); //如果当前小内存块都不能分配,就创建一块新的
}
static void* ngx_malloc_large(ngx_pool_t* pool,int size){ //分配一块大块内存
printf("----------------------ngx_malloc_large--------------------\n");
if(check(pool)) exit(-1);
int count=0;
ngx_pool_large_t* large=NULL;
void* ret=malloc(size);
for(large=pool->large;large;large=large->next){ //如果有空闲链表,就直接挂载上去
if(large->alloc==NULL){
large->alloc=ret;
return ret;
}
if(++count>MAX_COUNT) break;
}
large=ngx_malloc_small(pool,sizeof(ngx_pool_large_t)); //在小内存块里分配大内存块的结构体
if(check(pool)) exit(-1);
large->alloc=ret; //插入链表,头插法
large->next=pool->large;
pool->large=large;
return ret;
}
static void* ngx_malloc_block(ngx_pool_t* pool,int size){ //分配一块新的小内存块
printf("----------------------ngx_malloc_block--------------------\n");
if(check(pool)) exit(-1);
int psize=(ngx_pool_t*)pool->d.end-pool; //分配一块与第一块一样大的内存块
ngx_pool_t* new_block=(ngx_pool_t*)malloc(psize);
new_block->d.last=new_block+sizeof(ngx_pool_data_t); //内存块开头只放一个 ngx_pool_data_t 结构体,与首节点区分开
new_block->d.end=new_block+psize;
new_block->d.fails=0;
ngx_pool_t* tmp=pool->current;
for(tmp;tmp->d.next;tmp=tmp->d.next){ //之前的块都分配失败,失败次数加一
if(++tmp->d.fails>MAX_FAILS) //如果失败次数到达上限,就不将该块作为当前使用的块
pool->current=tmp->d.next;
}
tmp->d.next=new_block; //尾插法
return new_block->d.last;
}
void ngx_destroy_pool(ngx_pool_t* pool){ //销毁内存池
printf("----------------------ngx_pool_destroy--------------------\n");
if(check(pool)) exit(-1);
ngx_pool_t *p, *n;
ngx_pool_large_t *l;
for (l = pool->large; l; l = l->next) {
if (l->alloc) {
free(l->alloc);
}
}
for (p = pool, n = pool->d.next; ;p = n, n = n->d.next) {
if(p) free(p);
if (n == NULL) {
break;
}
}
return;
}
~
~
#include "mem_pool.h"
int main(void){
long i=0;
ngx_pool_t* pool= ngx_create_pool(4048); //初始化内存池
if(1){
for(i;i<10;++i){
char* tmp=(char*)ngx_malloc(pool,10); //分配内存
memset(tmp,'A',10);
printf("tmp=%s\n",tmp);
}
ngx_destroy_pool(pool);
}
else{
for(i;i<10000000;++i){
int* tmp=(int*)malloc(250);
}
}
return 1;
}
~
Nginx 内存池