ngx内存池设计概阅

看下 ngx的内存池,目前发现和STL相差不多

把内存分配归结为大内存分配和小内存分配。若申请的内存大小比同页的内存池最大值 max 还大,则是大内存分配,否则为小内存分配。

  1. 大块内存的分配请求不会直接在内存池上分配内存来满足请求,而是直接向系统申请一块内存(就像直接使用 malloc 分配内存一样),然后将这块内存挂到内存池头部的 large 字段下。
  2. 小块内存分配,则是从已有的内存池数据区中分配出一部分内存。
/*
为了减少内存碎片的数量,并通过统一管理来减少代码中出现内存泄漏的可能性,Nginx设计了ngx_pool_t内存池数据结构。
*/
/*
内存池           ---  ngx_pool_s;
内存块数据       ---  ngx_pool_data_t;
大内存块         --- ngx_pool_large_s; 
*/
//内存池数据结构,链表形式存储   图形化理解参考Nginx 内存池(pool)分析 http://www.linuxidc.com/Linux/2011-08/41860.htm
struct ngx_pool_s {
    ngx_pool_data_t       d;//节点数据    // 包含 pool 的数据区指针的结构体 pool->d.last ~ pool->d.end 中的内存区便是可用数据区。
    size_t                max;//当前内存节点可以申请的最大内存空间 // 一次最多从pool中开辟的最大空间
    //每次从pool中分配内存的时候都是从curren开始遍历pool节点获取内存的
    ngx_pool_t           *current;//内存池中可以申请内存的第一个节点      pool 当前正在使用的pool的指针 current 永远指向此pool的开始地址。current的意思是当前的pool地址

/*
pool 中的 chain 指向一个 ngx_chain_t 数据,其值是由宏 ngx_free_chain 进行赋予的,指向之前用完了的,
可以释放的ngx_chain_t数据。由函数ngx_alloc_chain_link进行使用。
*/
    ngx_chain_t          *chain;// pool 当前可用的 ngx_chain_t 数据,注意:由 ngx_free_chain 赋值   ngx_alloc_chain_link
    ngx_pool_large_t     *large;//节点中大内存块指针   // pool 中指向大数据快的指针(大数据快是指 size > max 的数据块)
    ngx_pool_cleanup_t   *cleanup;// pool 中指向 ngx_pool_cleanup_t 数据块的指针 //cleanup在ngx_pool_cleanup_add赋值
    ngx_log_t            *log; // pool 中指向 ngx_log_t 的指针,用于写日志的  ngx_event_accept会赋值
};
/*
内存池           ---  ngx_pool_s;
内存块数据       ---  ngx_pool_data_t;
大内存块         --- ngx_pool_large_s; 
*/ //内存块包含的数据   
typedef struct {
    u_char               *last;//申请过的内存的尾地址,可申请的首地址    pool->d.last ~ pool->d.end 中的内存区便是可用数据区。
    u_char               *end;//当前内存池节点可以申请的内存的最终位置  
    ngx_pool_t           *next;//下一个内存池节点ngx_pool_t,见ngx_palloc_block
    ngx_uint_t            failed;//当前节点申请内存失败的次数,   如果发现从当前pool中分配内存失败四次,则使用下一个pool,见ngx_palloc_block 
} ngx_pool_data_t;

 NGX_POOL_ALIGNMENT:对其!!为了落在一个cacheline 上?? 为啥值为16 不是64 

#define NGX_POOL_ALIGNMENT       16

/*
ngx_create_pool:创建pool
ngx_destory_pool:销毁 pool
ngx_reset_pool:重置pool中的部分数据
ngx_palloc/ngx_pnalloc:从pool中分配一块内存
ngx_pool_cleanup_add:为pool添加cleanup数据
*/
ngx_pool_t *
ngx_create_pool(size_t size, ngx_log_t *log)
{
    ngx_pool_t  *p;

    p = ngx_memalign(NGX_POOL_ALIGNMENT, size, log); //// 分配一块 size 大小的内存  内存空间16字节对齐
    if (p == NULL) {
        return NULL;
    }

    // 对pool中的数据项赋初始值
    p->d.last = (u_char *) p + sizeof(ngx_pool_t); //可用空间要减去这个头部 首sizeof(ngx_pool_t)便是pool的header信息,header信息中的各个字段用于管理整个pool
    p->d.end = (u_char *) p + size;
    p->d.next = NULL;
    p->d.failed = 0;

    size = size - sizeof(ngx_pool_t);
    p->max = (size < NGX_MAX_ALLOC_FROM_POOL) ? size : NGX_MAX_ALLOC_FROM_POOL; //不能超过NGX_MAX_ALLOC_FROM_POOL// pool 中最大可用大小
    
    p->current = p;
    p->chain = NULL;
    p->large = NULL;
    p->cleanup = NULL;
    p->log = log;

    return p; //指向空间最顶部头部
}

  

  ngx_palloc函数将会从pool内存池中分配到size字节的内存,并返回这段内存的起始地址。如果返回NULL空指针,则表示分配失败。

还有一个封装了ngx_palloc的函数ngx_pcalloc,它多做了一件事,就是把ngx_palloc申请到的内存块全部置为0,虽然,多数情况下更适合用ngx_pcalloc来分配内存。

void *
ngx_palloc(ngx_pool_t *pool, size_t size)
{
    u_char      *m;
    ngx_pool_t  *p;
    
    // 判断 size 是否大于 pool 最大可使用内存大小
    if (size <= pool->max) {

        p = pool->current; //从current所在的pool数据节点开始往后遍历寻找那个节点可以分配size内存

        do {
            m = ngx_align_ptr(p->d.last, NGX_ALIGNMENT);// 将 m 对其到内存对齐地址
            if ((size_t) (p->d.end - m) >= size) {// 判断 pool 中剩余内存是否够用
                p->d.last = m + size;

                return m;
            }

            p = p->d.next;//如果当前内存不够,则在下一个内存快中分配空间

        } while (p);

        return ngx_palloc_block(pool, size);
    }

    /*
    我们讨论最后一种情况,当需要的内存大于pool最大可分配内存大小时,此时首先判断size已经大于pool->max的大小了,所以直接调用ngx_palloc_large进行大内存分配,我们将注意力转向这个函数
    本篇文章来源于 Linux公社网站(www.linuxidc.com)  原文链接:http://www.linuxidc.com/Linux/2011-08/41860.htm
    */
    return ngx_palloc_large(pool, size);
}
//在大多数平台上,NGX_ALIGNMENT is defined是unsigned long的大小

#define NGX_ALIGNMENT  _MAX_ALIGNMENT
//如果前面开辟的pool空间已经用完,则从新开辟空间ngx_pool_t
static void *
ngx_palloc_block(ngx_pool_t *pool, size_t size)
{
    u_char      *m;
    size_t       psize;
    ngx_pool_t  *p, *new;

    // 先前的整个 pool 的大小
    psize = (size_t) (pool->d.end - (u_char *) pool);

    //// 在内存对齐了的前提下,新分配一块内存
    m = ngx_memalign(NGX_POOL_ALIGNMENT, psize, pool->log);
    if (m == NULL) {
        return NULL;
    }

    new = (ngx_pool_t *) m;

    new->d.end = m + psize;
    new->d.next = NULL;
    new->d.failed = 0;

    m += sizeof(ngx_pool_data_t);
    m = ngx_align_ptr(m, NGX_ALIGNMENT);
    new->d.last = m + size;

    // 判断在当前 pool 分配内存的失败次数,即:不能复用当前 pool 的次数,
    // 如果大于 4 次,这放弃在此 pool 上再次尝试分配内存,以提高效率
    //如果失败次数大于4(不等于4),则更新current指针,放弃对老pool的内存进行再使用
    for (p = pool->current; p->d.next; p = p->d.next) {
        if (p->d.failed++ > 4) {
            pool->current = p->d.next;// 更新 current 指针, 每次从pool中分配内存的时候都是从curren开始遍历pool节点获取内存的
        }
    }

    // 让旧指针数据区的 next 指向新分配的 pool
    p->d.next = new;

    return m;
}

  

  ngx_align_ptr:作用不言而喻,取整可以降低CPU读取内存的次数,提高性能;防止数据结构落在连个cacheline 上;cpu 去地址时 取偶数! 为了取一块连续地址内存b-d  结果获取了 a-c   b-e软后合并 取到b-d数据??

  在最新的x86架构上,高速缓存行是64字节,这是可以从内存读取到高速缓存的最小数据量。 假设您的结构大小为56个字节,则其中有很多数组。 查找一个元素时,CPU将需要发出2个内存请求(即使它位于高速缓存行的中间,它也可能发出2个请求)。 这对性能不利,因为您必须等待内存,并且使用更多的缓存,这最终会提供更高的缓存丢失率。 在这种情况下,仅使用posix_memalign是不够的,但是您应该填充或压缩结构以使其位于64字节边界上。 

  在GNU系统中,malloc或realloc返回的内存块地址都是8的倍数(如果是64位系统,则为16的倍数)。如果你需要更大的粒度,使用memalign。这些函数在头文件“stdlib.h”中声明。

在GNU库中,可以使用函数free释放memalign返回的内存块。
     函数:void * memalign (size_t boundary, size_t size) 
     函数memalign将分配一个由size指定大小,地址是boundary的倍数的内存块。参数boundary必须是2的幂!函数memalign可以分配较大的内存块,并且可以为返回的地址指定粒度。

所以 内存池肯定需要对其哦!

  ngx_palloc 和 ngx_pnalloc 都是从内存池里分配 size 大小内存。他们的不同之处在于,palloc 取得的内存是对齐的,pnalloc 则不考虑内存对齐问题。ngx_pcalloc 是直接调用 palloc 分配内存,然后进行一次 0 初始化操作。ngx_pmemalign 将在分配 size 大小的内存并按 alignment 对齐,然后挂到 large 字段下,当做大块内存处理。

  ngx_palloc的过程一般为,首先判断待分配的内存是否大于 pool->max,如果大于则使用 ngx_palloc_large 在 large 链表里分配一段内存并返回, 如果小于测尝试从链表的 pool->current 开始遍历链表,尝试找出一个可以分配的内存,当链表里的任何一个节点都无法分配内存的时候,就调用 ngx_palloc_block 生成链表里一个新的节点, 并在新的节点里分配内存并返回, 同时, 还会将pool->current 指针指向新的位置(从链表里面pool->d.failed小于等于4的节点里找出) 。

 

 

  上图是由3个小内存池组成的内存池模型,由于第一个内存池上剩余的内存不够分配了,于是就创建了第二个新的内存池,第三个内存池是由于前面两个内存池的剩余部分都不够分配,所以创建了第三个内存池来满足用户的需求。由图可见:所有的小内存池是由一个单向链表维护在一起的。这里还有两个字段需要关注,failed和current字段。failed表示的是当前这个内存池的剩余可用内存不能满足用户分配请求的次数,即是说:一个分配请求到来后,在这个内存池上分配不到想要的内存,那么就failed就会增加1;这个分配请求将会递交给下一个内存池去处理,如果下一个内存池也不能满足,那么它的failed也会加1,然后将请求继续往下传递,直到满足请求为止(如果没有现成的内存池来满足,会再创建一个新的内存池)。current字段会随着failed的增加而发生改变,如果current指向的内存池的failed达到了4的话,current就指向下一个内存池了。

大块内存分配

static void *
ngx_palloc_large(ngx_pool_t *pool, size_t size)
{
    void              *p;
    ngx_uint_t         n;
    ngx_pool_large_t  *large;

    /*
    // 重新申请一块大小为 size 的新内存
    // 注意:此处不使用 ngx_memalign 的原因是,新分配的内存较大,对其也没太大必要
    //  而且后面提供了 ngx_pmemalign 函数,专门用户分配对齐了的内存
    */
    p = ngx_alloc(size, pool->log);
    if (p == NULL) {
        return NULL;
    }

    n = 0;

    // 查找largt链表上空余的large 指针
    for (large = pool->large; large; large = large->next) {
        if (large->alloc == NULL) { //就用这个没用的large
            large->alloc = p;
            return p;
        }

        /*
         // 如果当前 large 后串的 large 内存块数目大于 3 (不等于3),
        // 则直接去下一步分配新内存,不再查找了
        */
        if (n++ > 3) {//也就是说如果pool->large头后面连续4个large的alloc指针都被用了,则重新申请一个新的pool_larg并放到pool->large头部
            break; //????? 感觉没啥用,因为后面每次alloc的large对应的alloc都是赋值了的
        }
    }

    large = ngx_palloc(pool, sizeof(ngx_pool_large_t));
    if (large == NULL) {
        ngx_free(p);
        return NULL;
    }

    // 将新分配的 large 串到链表后面
    large->alloc = p;
    large->next = pool->large;
    pool->large = large;

    return p;
}
struct ngx_pool_large_s { //ngx_pool_s中的大块内存成员
    ngx_pool_large_t     *next;
    void                 *alloc;//申请的内存块地址   
};

目前发现和STL的内存管理差不多!!

http代理服务器(3-4-7层代理)-网络事件库公共组件、内核kernel驱动 摄像头驱动 tcpip网络协议栈、netfilter、bridge 好像看过!!!! 但行好事 莫问前程 --身高体重180的胖子
原文地址:https://www.cnblogs.com/codestack/p/14780330.html