1. 程式人生 > >nginx的記憶體池設計

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