nginx内存池原理和使用场景

290 阅读3分钟

一 说明

本文简短分析nginx中内存池实现和使用场景,以讲解原理为主,忽略部分细节,nginx内存相对简陋,能挖掘东西不多,仅做个人自娱自乐,图片来自网络,如有侵权就换一张

fdc4479ebdce1c29829cb14dd5a7964d.png

二 内存池简要

内存池技术是一次性开辟大块内存,管理应用层对内存的统一分配和释放。能有效防止内存泄漏和内存碎片,nginx内存池相当简单,传统的链式管理的定长内存块(包括小内存快和大内存两条链表),小内存块不及时回收,在使用完毕后才统一销毁。

三 主要常量

/* 从内存池申请内存的大小分界点 */
#define NGX_MAX_ALLOC_FROM_POOL  (ngx_pagesize - 1)

四 关键数据结构

/* 内存块头部 */
typedef struct {
    u_char               *last;     -- 指向可用内存的起始地址
    u_char               *end;      -- 指向可用内存的末尾地址 
    ngx_pool_t           *next;     -- 指向下一个内存块
    ngx_uint_t            failed;   -- 当前内存块分配空间失败次数 
} ngx_pool_data_t;

/* 内存池 */
struct ngx_pool_s {
    ngx_pool_data_t       d;        -- 内存块头部信息   
    size_t                max;      -- 内存池中能分配的最大空间
    ngx_pool_t           *current;  -- 指向当前分配空间的内存块地址 
    ngx_chain_t          *chain;    -- 挂接的ngx_chain_t类型的指针,扩展使用
    ngx_pool_large_t     *large;    -- 大内存块的入口指针
    ngx_pool_cleanup_t   *cleanup;  -- 内存池清理操作指针
    ngx_log_t            *log;      
};

struct ngx_pool_large_s {
    ngx_pool_large_t     *next;     -- 下一个大内存块地址
    void                 *alloc;    -- 大内存分配的内存地址

};

五 核心函数

# 1 创建内存池流程如下

   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);
        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;
   }
  
   

# 2 销毁内存池流程如下

   void ngx_destroy_pool(ngx_pool_t *pool)
    {
        ngx_pool_t          *p, *n;
        ngx_pool_large_t    *l;

        // 释放大内存块链表
        for (l = pool->large; l; l = l->next) {
            if (l->alloc) {
                ngx_free(l->alloc);
            }
        }

        // 释放小内存块链  
        for (p = pool, n = pool->d.next; /* void */; p = n, n = n->d.next) {
            ngx_free(p);

            if (n == NULL) {
                break;
            }
        }
    }
 
    

# 2 申请分配内存

    nginx中根据申请内存的大小分析调用以下两个函数
    static void *ngx_palloc_small(ngx_pool_t *pool, size_t size, ngx_uint_t align)
    {
        u_char      *m;
        ngx_pool_t  *p;
        p = pool->current;
        
        // 从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);

        // 找不到合适的创建新的内存块
        return ngx_palloc_block(pool, size);
    }

    static void * ngx_palloc_block(ngx_pool_t *pool, size_t size)
    {
        u_char      *m;
        size_t       psize;
        ngx_pool_t  *p, *new, *current;

        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;
        
        // 将所有旧内存块标记分配失败+1,标记失败4次数后不再分配这些内存块 
        current = pool->current;
        for (p = current; p->d.next; p = p->d.next) {
            if (p->d.failed++ > 4) {
                current = p->d.next;
            }
        }

        // 将新开辟的内存块挂载在小内存块链表尾部
        p->d.next = new;
        pool->current = current ? current : new;

        return m;
    }


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

        // 使用系统函数开辟内存
        p = ngx_alloc(size, pool->log);
        if (p == NULL) {
            return NULL;
        }

        // 在大内存块链表中查看是否有被释放的内存块(只查前3个),如果有直接挂载上去
        n = 0;
        for (large = pool->large; large; large = large->next) {
            if (large->alloc == NULL) {
                large->alloc = p;
                return p;
            }

            if (n++ > 3) {
                break;
            }
        }

        // 在小内存块申请头部信息 
        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;
    }
 
        

# 4 释放内存

    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;
    }
        
        

六 内存池在nginx中使用场景