Nginx 内存池

Nginx 内存池

    • 前言
    • 一.高并发下传统方式的弊端
        • 1.高并发时频繁的内存分配导致系统调用频繁
        • 2.频繁使用时增加了系统内存的碎片
        • 3. 没有垃圾回收机制
    • 二.解决方法
        • 1.系统层面
        • 2.应用层面
    • 三.Nginx内存池
        • 1.什么是内存池
        • 2.实现思路
        • 3.Nginx 内存池结构图
        • 4.数据结构详解
        • 5.代码解析
          • 1.mem_pool.h
          • 2.mem_pool.c
          • 3.main.c
    • 四.完整代码文件:

前言

最近学习了Nginx的内存池实现。想着总结记录一下。

一.高并发下传统方式的弊端

首先为什么我们需要使用内存池,就是因为传统方式有很多弊端:

1.高并发时频繁的内存分配导致系统调用频繁

我们知道Linux内存模型中有用户态和内核态之分。而让系统分配内存需要执行系统调用,而要执行系统调用就要产生中断,就势必会有用户态到内核态的切换。而这个切换又是很耗时间,如果频繁的执行就会降低程序的效率。由于我们分配内存时都是需要多少分配多少,如果在某个循环中分配内存就会造成从用户态到内核态的频繁切换。这是传统方式的弊端之一,如果追求高效率,这种方法显然不行。

2.频繁使用时增加了系统内存的碎片

产生根源:

1.内存分配必须起始于可被 4、8 或 16 整除(视处理器体系结构而定)的地址

2.MMU的分页机制的限制

由于使用malloc函数分配内存时,系统并不会分配连续的内存。也就是说,如果我们分配了一块内存,它的起始地址是0X123456,结束地址是0X123480,分配下一块内存时,下一块内存的地址并不是从0X123480开始的。这中间有间隔。也就是内存碎片。显然,这会降低内存的利用率。这是传统方式的又一弊端。

3. 没有垃圾回收机制

在C语言中我们使用malloc分配内存,用free释放内存。malloc的内存一定要free掉,不然就会造成内存泄露。但在实际情况下我们可能会忘记释放或未释放完全。这就容易导致内存枯竭。

二.解决方法

1.系统层面

要系统分配内存需要调用 Malloc 函数,但前面提到过通过 Malloc 函数分配内存会存在很多问题。那么有没有其它可以替代 Malloc 函数的函数呢。答案是肯定的。看下图:

Nginx 内存池_第1张图片
其中 PtMalloc 即是 Linux 默认使用的内存分配函数,简称 Malloc 。而 TcMalloc 与 JeMalloc 要使用则需要链接动态库。普通情况下使用 Malloc 函数没问题。但在多线程高效内存分配下是 TcMalloc 与 JeMalloc 效率更高。

2.应用层面

也就是我们今天的主角内存池。可根据应用自己定制。

三.Nginx内存池

1.什么是内存池

摘自百度百科:

内存池则是在真正使用内存之前,先申请分配一定数量的、大小相等(一般情况下)的内存块留作备用。当有新的内存需求时,就从内存池中分出一部分内存块,若内存块不够再继续申请新的内存。这样做的一个显著优点是,使得内存分配效率得到提升。
在内核中有不少地方内存分配不允许失败. 作为一个在这些情况下确保分配的方式, 内核开发者创建了一个已知为内存池(或者是 “mempool” )的抽象. 一个内存池真实地只是一类后备缓存, 它尽力一直保持一个空闲内存列表给紧急时使用.

池化技术一向如此,先提供足量的资源放在池中,当某个对象需要时,就从池中取得资源并使用。
而使用内存池最大的优点就是避免频繁的系统调用,提高效率,同时减少内存碎片的产生。

2.实现思路

1.每个请求或者连接都会建立相应的内存池,建立好内存池之后,我们可以直接从内存池中申请所需要的内存,不用去管内存的释放,当内存池使用完成之后一次性销毁内存池。

2.区分大小内存块的申请和释放,大于池尺寸的定义为大内存块,使用单独的大内存块链表保存,即时分配和释放;小于等于池尺寸的定义为小内存块,直接从预先分配的内存块中提取,不够就扩充池中的内存,在生命周期内对小块内存不做释放,直到最后统一销毁。

3.Nginx 内存池结构图

Nginx 内存池_第2张图片

4.数据结构详解

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;

5.代码解析

1.mem_pool.h
#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);
~

2.mem_pool.c
#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;
}
~
~

3.main.c
#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 内存池

你可能感兴趣的:(池式组件,c++,后端)