nginx_pool的實現解析

這裏只介紹nginx_pool主要的大小內存申請、回收及其高效的內存分配機制具體的實現。nginx

1.nginx_create_pool(size_t size, ngx_log_t *log)

這個函數是內存池的建立函數。 第一個參數是內存池的大小(一次最大可申請的小塊空間大小),其實實際的小塊空間單次最大可申請大小還須要用size減去sizeof(ngx_pool_t)(內存池頭部結構體的大小):
struct ngx_pool_s {
    ngx_pool_data_t       d;  //內存池數據塊信息
    size_t                max; //小塊內存的最大大小
    ngx_pool_t           *current;  //最近一個能夠分配小塊內存的內存塊
    ngx_chain_t          *chain;
    ngx_pool_large_t     *large;  //大塊內存鏈表
    ngx_pool_cleanup_t   *cleanup; //析構函數鏈表
    ngx_log_t            *log;   //日誌信息
};

/*內存池數據塊信息*/
typedef struct {
    u_char               *last; //這一塊內存塊中能夠分配出去的內存地址
    u_char               *end; //指向這一塊內存最後
    ngx_pool_t           *next;  //下一個內存塊
    ngx_uint_t            failed;  //在這一塊內存塊上分配失敗的次數
} ngx_pool_data_t;

因此size最小應該爲sizeof(ngx_pool_t),其最大不能超過NGX_MAX_ALLOC_FROM_POOL:函數

#define NGX_MAX_ALLOC_FROM_POOL  (ngx_pagesize - 1)

也就是最大不能夠超過ngx_pagesize - 1。第二個參數是日誌信息參數。ui

(1)p = ngx_memalign(NGX_POOL_ALIGNMENT, size, log);

ngx_memalign()是爲內存池的建立申請內存的函數,它實際調用的是ngx_alloc()函數:
#define ngx_memalign(alignment, size, log)  ngx_alloc(size, log)
ngx_alloc()函數的實現:
void *ngx_alloc(size_t size, ngx_log_t *log)
{
    void  *p;

    p = malloc(size);  //具體申請內存是經過malloc
    if (p == NULL) {
        ngx_log_error(NGX_LOG_EMERG, log, ngx_errno,
                      "malloc(%uz) failed", size);
    }

    ngx_log_debug2(NGX_LOG_DEBUG_ALLOC, log, 0, "malloc: %p:%uz", p, size);

    return p;
}

與ngx_alloc()函數類似的一個函數ngx_calloc(),比ngx_alloc多了一步初始化的操做,這一點和malloc和calloc比較類似。this

(2)內存池信息初始化(這裏直接看代碼比較容易):

p->d.last = (u_char *) p + sizeof(ngx_pool_t);// 1
     p->d.end = (u_char *) p + size;// 2
     p->d.next = NULL;// 3
     p->d.failed = 0;// 4
 
     size = size - sizeof(ngx_pool_t);// 5
     p->max = (size < NGX_MAX_ALLOC_FROM_POOL) ? size : NGX_MAX_ALLOC_FROM_POOL;// 6
 
     p->current = p;// 7
     p->chain = NULL;
     p->large = NULL;
     p->cleanup = NULL;
     p->log = log;
1:數據塊last指針指向ngx_pool_s結構體下方,表示可用空間的開頭。

2:數據塊end指針指向內存塊底部,表示可用空間的邊界。debug

3:下一個可用內存塊指針置NULL,由於內存池是增加的,剛開始只有一塊小內存,內存塊鏈是一個不夠再申請一個,這樣一個個增長上去的。因此這裏就是NULL。指針

4:failed這一塊內存塊上分配失敗的次數置零,當次數超過必定值時,這一塊的內存上的current就會指向一個下一個失敗次數低於限制的可用的內存塊。下一次直接能夠從可用的內存塊上分配,減小遍歷內存塊鏈的時間,我認爲這是ngx_pool在內存分配上採起的一個高效的措施。日誌

5:這個size是表示當前內存塊上實際可用的空間大小。減去ngx_pool_s的大小就是剩下的實際能夠用來分配的大小。code

6:max用來區分申請的大小是小塊內存仍是大塊內存。這裏對它的大小作了一個限制,就是最大爲ngx_pagesize - 1。對象

7:current表示下一個能夠分配的內存塊,初始化爲當前的內存塊,由於當前內存塊是可用的。內存

nginx_create_pool其實就作了兩件事,申請空間和數據初始化,也就是把內存池的頭部作好,爲之後的操做作鋪墊。

2.ngx_palloc_small(ngx_pool_t *pool, size_t size, ngx_uint_t align) 小塊內存分配函數

第一個參數是內存池頭部結構體指針,第二個是要申請的大小,第三個參數是一個內存對齊的標誌,1就是須要按內存對齊申請,0就是不內存對齊。

(1)ngx_align_ptr(p, a)

這是內存對齊函數,其實是一個宏:
#define ngx_align_ptr(p, a)                         
    (u_char *) (((uintptr_t) (p) + ((uintptr_t) a - 1)) & ~((uintptr_t) a - 1))

其中a是sizeof(unsigned long),也就是說32位系統按照四字節對齊,64位系統按照8字節對齊。這個對齊方式是這樣的,拿32位系統,按照四字節對齊方式:p + 3(0000 0000 0000 0011),若是p的最低兩位不是0(沒有對齊),便會向上進位,而後再與上~(0000 0000 0000 0011) = (1111 1111 1111 1100),這樣最低位的全爲0且會大於原來p,不會越界。若是最低兩位是0,那麼加上3再與上~(3),就仍是原來的值。這樣就實現了內存對齊。那麼爲何要內存對齊呢?由於內存對齊,能夠大大增長cpu讀取內存的效率,減小cpu沒必要要的I/O次數。

(2)再內存塊上循環找一個能夠分配的內存塊,從current開始。若是沒有合適的,從新申請一塊和以前的同樣大小的內存塊用於這一次的分配。內存池的增加就是這一步:

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); //新申請的內存塊大小應該跟原來的同樣大小

    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; //分配申請的小內存

    /*這裏的循環就是把current移到failed次數低於四次的小內存塊上*/
    for (p = pool->current; p->d.next; p = p->d.next) {
        if (p->d.failed++ > 4) {
            pool->current = p->d.next;
        }
    }
    

    p->d.next = new; //新的內存塊連到小快內存鏈上

    return m;
}

3.static void ngx_palloc_large(ngx_pool_t pool, size_t size)

大塊內存分配函數。第一個參數是內存池頭pool,第二個參數是大小。

(1)第一步申請大塊內存,經過ngx_alloc(),其實底層是經過malloc申請的。

p = ngx_alloc(size, pool->log);

(2)第二步是將申請的大塊內存地址放到ngx_pool_large_t結構體中。

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;  //當前結構體插入到大塊內存鏈
這一步是在大塊內存鏈上找一個空的大塊內存結構體把當前的大塊內存地址放到裏面進行管理,這裏的n,就是又一個高效的地方,爲了減小遍歷鏈表的時間,只要遍歷超過三次,便再也不查找,會在小塊內存上申請一個小空間來管理這塊大內存,而後把大塊內存結構體插入到當前的鏈表上。

4.ngx_int_t ngx_pfree(ngx_pool_t pool, void p)

大塊內存釋放函數。第一個參數是內存池pool,第二個是要釋放的內存地址。

這裏的大塊內存是用malloc申請的,因此釋放要用free。釋放以後,大塊內存的結構體不用釋放,由於是從小塊內存上申請的空間,因此是沒辦法釋放的,這樣也有一個好處就是之後的大塊內存能夠節省申請空間的時間,更加高效。

5.void ngx_destroy_pool(ngx_pool_t *pool)

內存池銷燬函數。若是說內存池是一個對象,那麼銷燬函數就是一個析構函數,把全部資源釋放,可是仍是須要把小塊內存上的全部對象都析構。而後再free大塊的內存,最後就是釋放全部小塊內存。下面是源碼:

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


/*debugger模式下對內存池的跟蹤*/
#if (NGX_DEBUG) 

    /*
     * we could allocate the pool->log from this pool
     * so we cannot use this log while free()ing the pool
     */

    for (l = pool->large; l; l = l->next) {
        ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, pool->log, 0, "free: %p", l->alloc);
    }

    for (p = pool, n = pool->d.next; /* void */; p = n, n = n->d.next) {
        ngx_log_debug2(NGX_LOG_DEBUG_ALLOC, pool->log, 0,
                       "free: %p, unused: %uz", p, p->d.end - p->d.last);

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

#endif
    /*釋放全部大塊內存*/
    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;
        }
    }
}

6.void ngx_reset_pool(ngx_pool_t *pool)

內存池重置函數。參數只有一個要重置的內存池。

reset和destory的區別在於,reset不用釋放全部小塊內存,重置以後能夠繼續使用;destory以後,這個內存池的全部資源都釋放,內存池已經不存在了。因此reset須要釋放全部大塊內存釋放,而後把全部的小塊重置爲可分配狀態。

源碼:

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);
        }
    }
    /*把全部小塊內存*/
    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;
}
相關文章
相關標籤/搜索