1. 程式人生 > >nginx源碼分析——內存池

nginx源碼分析——內存池

line ptr del bug efi 自身 free 填充 res

ngx_palloc.h

/*
 * Copyright (C) Igor Sysoev
 * Copyright (C) Nginx, Inc.
 */


#ifndef _NGX_PALLOC_H_INCLUDED_
#define _NGX_PALLOC_H_INCLUDED_


#include <ngx_config.h>
#include <ngx_core.h>


// 內存池界定小內存與大內存的邊界值的最大值。
/*
 * NGX_MAX_ALLOC_FROM_POOL should be (ngx_pagesize - 1), i.e. 4095 on x86.
 * On Windows NT it decreases a number of locked pages in a kernel.
 
*/ #define NGX_MAX_ALLOC_FROM_POOL (ngx_pagesize - 1)
// 未知 #define NGX_DEFAULT_POOL_SIZE (16 * 1024) // 內存池的內存對齊值,即分配的內存大小是該值的倍數。 #define NGX_POOL_ALIGNMENT 16

// 未知 #define NGX_MIN_POOL_SIZE \ ngx_align((sizeof(ngx_pool_t) + 2 * sizeof(ngx_pool_large_t)), NGX_POOL_ALIGNMENT)
// 回收方法的模型 typedef void (*ngx_pool_cleanup_pt)(void *data); // 回收節點 typedef struct ngx_pool_cleanup_s ngx_pool_cleanup_t; struct ngx_pool_cleanup_s { // handler,回收方法 ngx_pool_cleanup_pt handler; // data指針,指向用於回收方法的傳入參數 void *data; // next指針,指向下一個回收節點 ngx_pool_cleanup_t *next; };
// 大內存節點 typedef struct ngx_pool_large_s ngx_pool_large_t; struct ngx_pool_large_s { // next指針,指向下一個大內存節點 ngx_pool_large_t *next; // alloc指針,指向大內存 void *alloc; }; // 小內存塊節點 typedef struct { // last指針,指向內存塊裏已用空間的結束位,亦是可用空間的開始位。 u_char *last; // end指針,指向內存塊的結束位。 u_char *end; // next指針,指向下一個內存塊。雖然類型是內存池,但實際應用時視作內存塊類型。 ngx_pool_t *next; // failed,統計內存塊因剩余空間不足以達到所需分配的內存大小而失敗,且需要新建內存塊才能分配所需內存的次數。 // 這個次數達到6次(從0累計到5)時,則下一次分配內存時,不管此內存塊的剩余空間是否足夠,都不會在此內存塊上分配內存。 ngx_uint_t failed; } ngx_pool_data_t; // 內存池 struct ngx_pool_s { // d,內存池的第一個小內存塊,意味著內存池自身是存儲於第一個小內存塊裏。 ngx_pool_data_t d; // max邊界值,用於界定分配的內存是小內存還是大內存。 // max邊界值,先受限於最大界值,再受限於初始內存大小。 size_t max; // current指針,指向第一個可分配內存的小內存塊。 ngx_pool_t *current; ngx_chain_t *chain; // large指針,指向大內存節點鏈頭. ngx_pool_large_t *large; // cleanup指針,指向回收節點鏈頭 ngx_pool_cleanup_t *cleanup; ngx_log_t *log; }; // 文件類型的回收節點時,其data所需該類型 typedef struct { // 文件句柄 ngx_fd_t fd; // 文件名稱 u_char *name; ngx_log_t *log; } ngx_pool_cleanup_file_t; // 創建內存池 ngx_pool_t *ngx_create_pool(size_t size, ngx_log_t *log); // 銷毀內存池 void ngx_destroy_pool(ngx_pool_t *pool); // 重置內存池 void ngx_reset_pool(ngx_pool_t *pool); // 從內存池裏分配內存(對齊內存) void *ngx_palloc(ngx_pool_t *pool, size_t size); // 分配內存(不對齊內存) void *ngx_pnalloc(ngx_pool_t *pool, size_t size); // 從內存池裏分配內存(對齊內存),並填充為0。 void *ngx_pcalloc(ngx_pool_t *pool, size_t size); // 從內存池裏分配大內存(內存對齊,不使用空閑內存節點) void *ngx_pmemalign(ngx_pool_t *pool, size_t size, size_t alignment); // 從內存池裏釋放指定的大內存(不釋放節點) ngx_int_t ngx_pfree(ngx_pool_t *pool, void *p); // 從內存池裏創建一個回收節點,當size大於0時,將創建給定大小的內存,但這個內存並不受內存池管理,即需要創建者自行釋放。 ngx_pool_cleanup_t *ngx_pool_cleanup_add(ngx_pool_t *p, size_t size); // 從內存池裏清理給定的文件類型的回收節點 void ngx_pool_run_cleanup_file(ngx_pool_t *p, ngx_fd_t fd); // 關閉文件 void ngx_pool_cleanup_file(void *data); // 刪除和關閉文件 void ngx_pool_delete_file(void *data); #endif /* _NGX_PALLOC_H_INCLUDED_ */

ngx_palloc.c

/*
 * Copyright (C) Igor Sysoev
 * Copyright (C) Nginx, Inc.
 */


#include <ngx_config.h>
#include <ngx_core.h>


static ngx_inline void *ngx_palloc_small(ngx_pool_t *pool, size_t size,
    ngx_uint_t align);
static void *ngx_palloc_block(ngx_pool_t *pool, size_t size);
static void *ngx_palloc_large(ngx_pool_t *pool, size_t size);


// 創建內存池
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;
    }

    // last指針,指向內存塊裏已用空間的結束位,亦是可用空間的開始位。
    p->d.last = (u_char *) p + sizeof(ngx_pool_t); // 內存池和內存塊各自占一部份內存。
    // end指針,指向內存塊的結束位。
    p->d.end = (u_char *) p + size;
    p->d.next = NULL;
    p->d.failed = 0;

    // max邊界值,用於界定分配的內存是小內存還是大內存。
    // max邊界值,先受限於最大界值,再受限於初始內存大小。
    size = size - sizeof(ngx_pool_t);
    p->max = (size < NGX_MAX_ALLOC_FROM_POOL) ? size : NGX_MAX_ALLOC_FROM_POOL;

    // current指針,表示當前內存塊,當前內存塊是處於可分配內存的狀態
    p->current = p;
    p->chain = NULL;
    p->large = NULL;
    p->cleanup = NULL;
    p->log = log;

    return p;
}


// 銷毀內存池
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) {
        // handler,回收方法,為NULL時表示已回收。
        if (c->handler) {
            ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, pool->log, 0,
                           "run cleanup: %p", c);
            // data,用於調用回收方法時傳入的參數。
            c->handler(c->data);
        }
    }

// 調試模式時,輸出日誌
#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;
        }
    }
}


// 重置內存池
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;
}

// 從內存池裏分配內存(對齊內存)
void *
ngx_palloc(ngx_pool_t *pool, size_t size)
{
#if !(NGX_DEBUG_PALLOC)
    // max邊界值,用於界定分配的內存是小內存還是大內存
    if (size <= pool->max) {
        // 分配小內存(對齊方式)
        return ngx_palloc_small(pool, size, 1);
    }
#endif
    // 分配大內存
    return ngx_palloc_large(pool, size);
}


// 分配內存(不對齊內存)
void *
ngx_pnalloc(ngx_pool_t *pool, size_t size)
{
#if !(NGX_DEBUG_PALLOC)
    // max邊界值,用於界定分配的內存是小內存還是大內存
    if (size <= pool->max) {
        // 分配小內存
        return ngx_palloc_small(pool, size, 0);
    }
#endif
    // 分配大內存
    return ngx_palloc_large(pool, size);
}


// 從內存池裏分配小內存(私有)
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;

    // 從第一個可分配內存的內存塊開始遍歷
    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) {
            // 如果足夠,則分配此內存塊
            // last指針,指向內存塊裏已用空間的結束位,亦是可用空間的開始位。
            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;

    // 創建同等大小的內存塊。
    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;

    // end指針,指向新內存塊的結束位。
    new->d.end = m + psize;
    new->d.next = NULL;
    new->d.failed = 0;

    // last指針,指向新內存塊裏已用空間的結束位,亦是可用空間的開始位。
    m += sizeof(ngx_pool_data_t);    // 新內存塊自身占用一部份內存
    m = ngx_align_ptr(m, NGX_ALIGNMENT);    // 進行內存對齊
    new->d.last = m + size;

    // 由於使用了新建內存塊進行分配內存,所以意味著內存池的所有可分配內存塊的剩余空間都不足以達到所需分配的內存大小。
    for (p = pool->current; p->d.next; p = p->d.next) {
        // failed,統計內存塊因剩余空間不足以達到所需分配的內存大小而失敗,且需要新建內存塊才能分配所需內存的次數。
        // 這個次數達到6次(從0累計到5)時,則下一次分配內存時,不管此內存塊的剩余空間是否足夠,都不會在此內存塊上分配內存。
        if (p->d.failed++ > 4) {
            // current指針,指向可分配內存的內存塊。
            pool->current = p->d.next;
        }
    }

    // 把新內存塊加入鏈尾
    p->d.next = new;

    // 返回分配的內存
    return m;
}

// 從內存池裏分配大內存(私有,使用前5個空閑內存節點)
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;
    }

    n = 0;
    for (large = pool->large; large; large = large->next) {
        // 判斷內存節點是否為空
        if (large->alloc == NULL) {
            // 如果為空,則綁定內存到該內存節點。
            // alloc指針,指向內存。
            large->alloc = p;
            // 返回內存
            return p;
        }

        // 累計未能找到空的內存塊節點的次數,如果達到5次(從0到4),則結束尋找。
        // 意味著空的內存節點最多存在5個。
        if (n++ > 3) {
            break;
        }
    }

    // 創建內存節點
    large = ngx_palloc_small(pool, sizeof(ngx_pool_large_t), 1);
    if (large == NULL) {
        ngx_free(p);
        return NULL;
    }
    
    // alloc指針,指向內存。
    large->alloc = p;
    // next指針,指向下一個內存節點。
    large->next = pool->large;
    // 把新的內存節點添加到鏈頭。
    pool->large = large;

    return p;
}


// 從內存池裏分配大內存(內存對齊,不使用空閑內存節點)
void *
ngx_pmemalign(ngx_pool_t *pool, size_t size, size_t alignment)
{
    void              *p;
    ngx_pool_large_t  *large;

    // 創建內存
    p = ngx_memalign(alignment, size, pool->log);
    if (p == NULL) {
        return NULL;
    }

    // 創建內存節點(內存對齊)
    large = ngx_palloc_small(pool, sizeof(ngx_pool_large_t), 1);
    if (large == NULL) {
        ngx_free(p);
        return NULL;
    }

    // alloc指針,指向內存。
    large->alloc = p;
    // next指針,指向下一個內存節點。
    large->next = pool->large;
    // 把新的內存節點添加到鏈頭。
    pool->large = large;

    return p;
}


// 從內存池裏釋放指定的大內存(不釋放節點)
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;
}


// 從內存池裏分配內存(對齊內存),並填充為0。
void *
ngx_pcalloc(ngx_pool_t *pool, size_t size)
{
    void *p;

    // 創建內存
    p = ngx_palloc(pool, size);
    if (p) {
        // 填充0
        ngx_memzero(p, size);
    }

    return p;
}

// 從內存池裏創建一個回收節點,當size大於0時,將創建給定大小的內存,但這個內存並不受內存池管理,即需要創建者自行釋放。
ngx_pool_cleanup_t *
ngx_pool_cleanup_add(ngx_pool_t *p, size_t size)
{
    ngx_pool_cleanup_t  *c;

    // 創建回收節點
    c = ngx_palloc(p, sizeof(ngx_pool_cleanup_t));
    if (c == NULL) {
        return NULL;
    }

    // data,用於調用回收方法時傳入的參數。
    if (size) {
        // 創建內存,但這個內存並不受內存池管理,即需要創建者自行釋放。
        c->data = ngx_palloc(p, size);
        // BUG?如果創建內存失敗,此時的回收節點還未加入到鏈頭,意味著回收節點不能釋放?
        if (c->data == NULL) {
            return NULL;
        }

    } else {
        c->data = NULL;
    }
    // handler,回收方法。
    c->handler = NULL;

    // 加入鏈頭
    c->next = p->cleanup;
    p->cleanup = c;

    ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, p->log, 0, "add cleanup: %p", c);

    return c;
}


// 從內存池裏清理給定的文件類型的回收節點
void
ngx_pool_run_cleanup_file(ngx_pool_t *p, ngx_fd_t fd)
{
    ngx_pool_cleanup_t       *c;
    ngx_pool_cleanup_file_t  *cf;

    for (c = p->cleanup; c; c = c->next) {
        if (c->handler == ngx_pool_cleanup_file) {
            // data,用於調用回收方法時傳入的參數。
            cf = c->data;
            if (cf->fd == fd) {
                // handler,回收方法,為NULL時表示已回收。
                c->handler(cf); // 這裏實際執行的方法是ngx_pool_cleanup_file
                c->handler = NULL;
                return;
            }
        }
    }
}

// 關閉文件
void
ngx_pool_cleanup_file(void *data)
{
    ngx_pool_cleanup_file_t  *c = data;

    ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, c->log, 0, "file cleanup: fd:%d",
                   c->fd);

    // 執行文件關閉
    // fd,文件句柄
    if (ngx_close_file(c->fd) == NGX_FILE_ERROR) {
        ngx_log_error(NGX_LOG_ALERT, c->log, ngx_errno,
                      ngx_close_file_n " \"%s\" failed", c->name);
    }
}


// 刪除和關閉文件
void
ngx_pool_delete_file(void *data)
{
    ngx_pool_cleanup_file_t  *c = data;

    ngx_err_t  err;

    ngx_log_debug2(NGX_LOG_DEBUG_ALLOC, c->log, 0, "file cleanup: fd:%d %s",
                   c->fd, c->name);

    // 執行文件刪除
    // name,文件名稱
    if (ngx_delete_file(c->name) == NGX_FILE_ERROR) {
        err = ngx_errno;

        if (err != NGX_ENOENT) {
            ngx_log_error(NGX_LOG_CRIT, c->log, err,
                          ngx_delete_file_n " \"%s\" failed", c->name);
        }
    }

    // 執行文件關閉
    // fd,文件句柄
    if (ngx_close_file(c->fd) == NGX_FILE_ERROR) {
        ngx_log_error(NGX_LOG_ALERT, c->log, ngx_errno,
                      ngx_close_file_n " \"%s\" failed", c->name);
    }
}


#if 0

static void *
ngx_get_cached_block(size_t size)
{
    void                     *p;
    ngx_cached_block_slot_t  *slot;

    if (ngx_cycle->cache == NULL) {
        return NULL;
    }

    slot = &ngx_cycle->cache[(size + ngx_pagesize - 1) / ngx_pagesize];

    slot->tries++;

    if (slot->number) {
        p = slot->block;
        slot->block = slot->block->next;
        slot->number--;
        return p;
    }

    return NULL;
}

#endif

nginx源碼分析——內存池