Nginx 源碼分析:ngx_pool_t

源代碼路徑

版本:1.8.0nginx

src\core\Ngx_palloc.h
src\core\Ngx_palloc.c

主要做用分析

提供了一種機制,幫助進行資源管理(內存、文件)。能夠類比C++中的RAII機制。數據結構

之內存管理爲例,一般是手工進行malloc/free,這種作法的優勢是靈活、高效,缺點是容易出錯,形成內存泄漏以及各類莫名其妙的BUG。函數

所以,在C/C++中正確的管理內存一直是最棘手的問題。ui

C++中提供了RAII機制和智能指針來解決這個問題。Nginx採用C語言開發,實現了一套用來管理資源的機制。這就是nginx poolspa

數據結構

ngx_pool_data_t指針

typedef struct {
    u_char               *last;
    u_char               *end;
    ngx_pool_t           *next;
    ngx_uint_t            failed;
} ngx_pool_data_t;

首先看一個示意圖:
圖片描述code

last指針表示ngx_pool_data_t所管理的內存block中已使用的內存的末尾地址,這也是下次分配的起始地址。接口

end指針表示ngx_pool_data_t所管理的內存block的尾地址,該指針在分配此block時肯定,用來標記last的邊界值。圖片

next用來指向下一個ngx_pool_data_t的內存block地址,用於造成block鏈表。
failed用來標記該block使用時分配失敗次數。內存

ngx_pool_data_t的管理和使用

圍繞核心數據結構ngx_pool_data_t, Nginx是如何管理和使用的呢?

本篇主要從如下幾個方面說明:
1) 如何初始化ngx_pool_data_t鏈表;
2) 如何向鏈表增長ngx_pool_data_t節點。
3) 如何回收/銷燬ngx_pool_data_t節點及鏈表

初始化ngx_pool_data_t鏈表

基本思路:

  1. 所謂初始化ngx_pool_data_t鏈表就是申請一段內存block,而後將該block指針void* p轉成ngx_pool_data_t*類型的指針,這樣,就能夠利用ngx_pool_data_t*指針來管理這段block,而這個block就是ngx_pool_data_t鏈表的節點
  2. 固然要可以正確管理block還須要初始化ngx_pool_data_t的成員變量lastendnextfailed。這樣,經過ngx_pool_data_t指針,能夠獲取管理ngx_pool_data_t鏈表節點的能力
  3. 可是如今咱們尚未管理整個ngx_pool_data_t鏈表的能力,那麼如何作呢?經過在內存block的起始部分添加lastendnextfailed等信息能夠管理一段內存。相應地,經過在ngx_pool_data_t鏈表第一個節點添加管理鏈表的信息,就能夠管理整個鏈表。

同時,因爲鏈表第一個節點只是一個特殊的節點因此,負責管理節點的結構體ngx_pool_data_t應該是的負責管理鏈表結構體節點的子集

Nginx源碼中這個管理鏈表的結構體就是:ngx_pool_s,其結構體定義爲:

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

其中,dngx_pool_data_t用來管理節點自己,而其餘變量則用來管理鏈表。

  • current用來指示鏈表中當前正在使用的節點;
  • chain用來在節點上掛接filter鏈表;
  • cleanup用來註冊資源回收handler等;
  • large是與節點大內存小內存有關,這裏暫不分析。

經過以上分析,可知,ngx_pool_s是管理整個ngx_pool_data_t鏈表,同時也可以管理所在的鏈表節點。

Nginx源碼裏,申請的block指針爲void*統一轉成ngx_pool_s*,這樣,在鏈表第一個節點,經過ngx_pool_s*管理整個鏈表及節點自己,在其餘節點,經過ngx_pool_s*管理節點自己。

根據以上思路,能夠很容易明白Nginx源碼裏關於建立ngx_pool_data_t鏈表的代碼

函數聲明: Ngx_palloc.h

ngx_pool_t *ngx_create_pool(size_t size, ngx_log_t *log);

說明:

輸入要分配的ngx_pool_t節點block大小size,返回一個ngx_pool_t*的指針。該指針既用來管理鏈表,也用來管理節點block自己。
所以,後續鏈表的插入,刪除都是經過操做該指針來完成,而使用鏈表第一個節點的block也是經過該指針來完成。

函數定義: Ngx_palloc.c

ngx_pool_t *
ngx_create_pool(size_t size, ngx_log_t *log)
{
    ngx_pool_t  *p;

    // 申請內存block,爲提升效率,進行了對齊
    // 轉成ngx_pool_t*指針
    p = ngx_memalign(NGX_POOL_ALIGNMENT, size, log);  
    if (p == NULL) {
        return NULL;
    }

    // 初始化ngx_pool_data_t用來管理節點block自己
    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;

    // 爲提升效率能使用的block最大值爲NGX_MAX_ALLOC_FROM_POOL
    // 輸入size最小值爲sizeof(ngx_pool_t),輸入小於該值會形成nginx崩潰
    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;
}

向鏈表增長ngx_pool_data_t節點

基本思路:

上一小節中關於如何初始化ngx_pool_data_t鏈表的思路,能夠基本套用到增長ngx_pool_data_t節點中來,只是在結構體ngx_pool_t成員變量初始化上有所區別。

直接上源碼:

static void *
ngx_palloc_block(ngx_pool_t *pool, size_t size)
{
    // 輸入變量pool用來表示整個鏈表
    u_char      *m;
    size_t       psize;
    ngx_pool_t  *p, *new;

    // 計算鏈表第一個節點的block大小
    psize = (size_t) (pool->d.end - (u_char *) pool);
    // 申請與鏈表第一個節點大小相同的內存block
    m = ngx_memalign(NGX_POOL_ALIGNMENT, psize, pool->log);
    if (m == NULL) {
        return NULL;
    }
    // 轉爲ngx_pool_t*類型
    new = (ngx_pool_t *) m;
    // 初始化節點管理結構體ngx_pool_data_t
    new->d.end = m + psize;
    new->d.next = NULL;
    new->d.failed = 0;
    // 爲了效率,對ngx_pool_data_t的last變量進行對齊操做
    m += sizeof(ngx_pool_data_t);
    m = ngx_align_ptr(m, NGX_ALIGNMENT);
    new->d.last = m + size;
    // 將第一個鏈表節點用於操做鏈表的pool指針的current變量指向當前節點
    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;
}

固然,在使用時,Nginx並非直接使用ngx_palloc_block來操做鏈表,而是使用ngx_palloc
緣由在於,使用ngx_create_poolngx_palloc_block構造鏈表以後,咱們獲得的是多個連續的內存塊組成的列表,這些內存塊大小相同,其大小爲調用ngx_create_pool傳入的size值或NGX_MAX_ALLOC_FROM_POOL

而在實際使用時malloc的大小不必定等於這些內存塊的大小,因此,須要提供一個接口,可以從ngx_pool_t鏈表中獲取須要大小的內存。

這個函數就是ngx_palloc

基本思路:

若是要獲取的內存大小大於ngx_pool_t節點的大小,那麼屬於建立大內存,調用ngx_palloc_large(pool, size)。這裏咱們暫不分析大內存的問題。

若是要獲取的內存小於ngx_pool_t節點的大小,那麼嘗試從當前節點分配。

這裏分兩種狀況:

  1. 當前節點剩餘空間足夠的狀況,直接分配;
  2. 當前節點剩餘空間不足的狀況,建立一個新節點,並將新節點分配

P.S 這裏的分配就是返回指針

函數聲明:

void *ngx_palloc(ngx_pool_t *pool, size_t size);

函數定義:

void *
ngx_palloc(ngx_pool_t *pool, size_t size)
{
    u_char      *m;
    ngx_pool_t  *p;
    // 獲取內存小於鏈表節點內存的狀況
    if (size <= pool->max) {

        p = pool->current;
        // 嘗試從當前節點分配內存
        do {
            m = ngx_align_ptr(p->d.last, 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);
    }
    // 獲取內存小於鏈表節點內存的狀況
    return ngx_palloc_large(pool, size);
}

今天先到這裏,後續再補充或另起一篇寫。

相關文章
相關標籤/搜索