nginx的内存池设计

  • 内存池结构体

    struct ngx_pool_s {
        ngx_pool_data_t       d; /* 内存池链表头指针 */
        size_t                max; /* 一个阈值,决定是用malloc分配内存还是从内存池分配 */
        ngx_pool_t           *current; /* 这个指针指向第一个可以分配小块内存的节点 */
        ngx_chain_t          *chain;
        ngx_pool_large_t     *large; /* 大于max用malloc分配内存,并生成ngx_pool_large_t结构体,大块内存链表头指针 */
        ngx_pool_cleanup_t   *cleanup; /* 需要释放的资源,fd,文件...,这也是个链表 */
        ngx_log_t            *log; /* 日志 */
    };
    

    nginx的内存池在分配空间时有两种方式,一种是从内存池分配,另外一种用malloc分配,
    分配方式的选择由预分配字节大小来决定,如果预分配字节大于max,就会使用malloc分配。
    整个内存池就是一个单链表,其通过ngx_pool_data_t这个结构体链起来的,对于这个单链
    表,每个节点都是个内存池,内存池大小一样,但是只有第一个内存池具有完整的ngx_pool_s
    结构体结构,其他内存池的只有一个ngx_pool_data_t结构体:

    typedef struct {
        u_char               *last; /* 内存池中当前未分配区域的首指针 */
        u_char               *end; /* 内存池尾指针 */
        ngx_pool_t           *next; /* 下一个ngx_pool_t */
        ngx_uint_t            failed; /* 分配的失败次数,即内存池剩余字节不足分配,这个与ngx_pool_s的current结构体有关 */
    } ngx_pool_data_t;
    

    内存池通过此结构体的next指针链接起来形成单链表
    创建内存池的函数:

    /* 创建内存池
        至少分配的内存要大于sizeof(ngx_pool_t) 
        分配的内存会挂载ngx_pool_data_t中 
        这个就是内存池链表的头结点,分配的内存的头部sizeof(ngx_pool_t)字节将会存储
    
        */
    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); /* 分配的内存要和NGX_POOL_ALIGNMENT对齐 */
        if (p == NULL) {
            return NULL;
        }
    
        p->d.last = (u_char *) p + sizeof(ngx_pool_t);
        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;
    
        p->current = p;
        p->chain = NULL;
        p->large = NULL;
        p->cleanup = NULL;
        p->log = log;
    
        return p;
    }
    

    再来看从内存池分配size(size <= max)字节的函数

    /* 按照align分配的内存是否和总线长度对齐 */
    static ngx_inline void *
    ngx_palloc_small(ngx_pool_t *pool, size_t size, ngx_uint_t align)
    {
        u_char      *m;
        ngx_pool_t  *p;
    
        /* 从current指针来遍历链表,而不是遍历整个链表 */
        p = pool->current;
    
        /* 从小块内存池链表中寻找剩余内存符合要求的空间 */
        do {
            m = p->d.last;
    
            if (align) {
                m = ngx_align_ptr(m, NGX_ALIGNMENT);
            }
    
            if ((size_t) (p->d.end - m) >= size) {
                p->d.last = m + size;
    
                return m;
            }
    
            p = p->d.next;
    
        } while (p);
    
        /* 所有ngx_pool_data_t的剩余空间都小与size了 */
        return ngx_palloc_block(pool, size);
    }
    
    /* 处理内存池链表上面所有节点的剩余空间全部不足size字节 */
    static void *
    ngx_palloc_block(ngx_pool_t *pool, size_t size)
    {
        u_char      *m;
        size_t       psize;
        ngx_pool_t  *p, *new;
    
        psize = (size_t) (pool->d.end - (u_char *) pool); /* 内存池加上sizeof(ngx_pool_t)的总字节大小
                                                                因为第一个ngx_pool_t的d.end指针指向内存池尾,
                                                                第一个ngx_pool_t的地址是内存池首 */
    
        /* 重新分配一个内存池 */
        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); /* 不需要是sizeof(ngx_pool_t),因为其他成员只需要在第一块内存池的中保存一份 */
        m = ngx_align_ptr(m, NGX_ALIGNMENT);
        new->d.last = m + size;
    
        /* 更新整个内存池链表的节点的分配失败次数 */
        for (p = pool->current; p->d.next; p = p->d.next) {
            /* 一旦内存池分配小块内存失败四次,currrent就会移到下一个内存池节点 */
            if (p->d.failed++ > 4) {
                pool->current = p->d.next;
            }
        }
        /* 将新分配的内存池节点挂到链表尾 */
        p->d.next = new;
    
        return m;
    }
    
  • 大块内存分配

    大块内存结构体
    struct ngx_pool_large_s {
        ngx_pool_large_t     *next; /* 链表下一节点 */
        void                 *alloc; /* 指向分配的大块内存 */
    };
    

在分配大块内存的时候,sizeof(ngx_pool_large_s)将会从内存池分配,alloc将会指向malloc分配的内存,
由于大块内存一般较大,nginx提供提前释放大块内存的函数,对于释放大块内存时,仅仅释放alloc指针指向的
内存,并将alloc置为NULL,在分配大块内存时,分配的内存块将会先遍历大块内存链表,找到是否有提前释放的
节点,如果有,就把分配的内存挂到此节点上,即alloc指针上。

接下来看分配大块内存的函数:

    /* 分配大块内存,返回其alloc指针 */
    static void *
    ngx_palloc_large(ngx_pool_t *pool, size_t size)
    {
        void              *p;
        ngx_uint_t         n;
        ngx_pool_large_t  *large;

        /* 大块内存直接使用malloc来分配 */
        p = ngx_alloc(size, pool->log);
        if (p == NULL) {
            return NULL;
        }

        n = 0;

        /* 大块内存将挂载到大块内存链表上
            首先找到有没有ngx_pool_large_t的alloc的内存已经释放 */
        for (large = pool->large; large; large = large->next) {
            if (large->alloc == NULL) {
                large->alloc = p;
                return p;
            }
            /* 最多只会遍历三个节点,因为前面的是最早分配的,也有可能最早释放,如果前面三个节点都
                没有释放,在遍历就有可能白费功夫 */
            if (n++ > 3) {
                break;
            }
        }

        /* ngx_pool_large_t需要的内存由小块内存分配 */
        large = ngx_palloc_small(pool, sizeof(ngx_pool_large_t), 1);
        if (large == NULL) {
            ngx_free(p);
            return NULL;
        }

        large->alloc = p;
        large->next = pool->large;
        pool->large = large;

        return p;
    }
  • 释放内存池及重置内存池

    /* 销毁整个内存池,ngx_pool_t是一个单链表 */
    void
    ngx_destroy_pool(ngx_pool_t *pool)
    {
        ngx_pool_t          *p, *n;
        ngx_pool_large_t    *l;
        ngx_pool_cleanup_t  *c;
    
        /* 释放文件句柄,删除文件等,首先删除必须删除/释放的资源 */
        for (c = pool->cleanup; c; c = c->next) {
            if (c->handler) {
                ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, pool->log, 0,
                               "run cleanup: %p", c);
                c->handler(c->data);
            }
        }
    
        /* 释放大块内存,仅仅释放alloc指针指向的内存,并不会释放ngx_pool_large_t结构体
            ngx_pool_large_t的释放由下面循环来释放 */
        for (l = pool->large; l; l = l->next) {
            if (l->alloc) {
                ngx_free(l->alloc);
            }
        }
        /* 释放ngx_pool_t结构体,这个首指针将指向分配的小块内存,因此这里会释放整个ngx_pool_t,也就是整个小的内存池
            ngx_pool_large_t的结构体占用的空间是从小内存池中分配的 */
        for (p = pool, n = pool->d.next; /* void */; p = n, n = n->d.next) {
            ngx_free(p);
    
            if (n == NULL) {
                break;
            }
        }
    }
    
    
    /* 重置pool
        大块内存释放,小块内存不释放,但是内存中的数据将忽略 */
    void
    ngx_reset_pool(ngx_pool_t *pool)
    {
        ngx_pool_t        *p;
        ngx_pool_large_t  *l;
        /* 释放大块内存 */
        for (l = pool->large; l; l = l->next) {
            if (l->alloc) {
                ngx_free(l->alloc);
            }
        }
        /* 小块内存将会重置,重置的过程中ngx_pool_large_t也会"释放" */
        for (p = pool; p; p = p->d.next) {
            p->d.last = (u_char *) p + sizeof(ngx_pool_t);
            p->d.failed = 0;
        }
    
        pool->current = pool;
        pool->chain = NULL;
        pool->large = NULL;
    }
    
    
    /* 提前释放ngx_pool_large_t的alloc内存 */
    ngx_int_t
    ngx_pfree(ngx_pool_t *pool, void *p)
    {
        ngx_pool_large_t  *l;
    
        for (l = pool->large; l; l = l->next) {
            if (p == l->alloc) {
                ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, pool->log, 0,
                               "free: %p", l->alloc);
                ngx_free(l->alloc);
                l->alloc = NULL;
    
                return NGX_OK;
            }
        }
    
        return NGX_DECLINED;
    }
    

猜你喜欢

转载自blog.csdn.net/w1157984197/article/details/81483228