Nginx 原始碼分析:ngx_array_t

_Zhao發表於2019-05-13

原始檔路徑

版本:1.8.0

srccoreNgx_array.h
srccoreNgx_array.c

主要作用分析

ngx_array_tNginx內部使用的陣列型資料結構,與C語言內建的陣列概念上類似,但是有兩點主要區別:

1)ngx_array_t使用ngx_pool_t記憶體池來管理記憶體;
2)ngx_array_t雖然有預設陣列大小的概念,但是在陣列元素超出預設值大小時,會在ngx_pool_t記憶體池中發生重分配。

但是需要指出,雖然ngx_array_t支援超出陣列預設值,但是在記憶體重分配之後並不會重新利用原來的記憶體,會造成部分記憶體浪費。

資料結構

ngx_array_t

typedef struct {
    void        *elts;
    ngx_uint_t   nelts;
    size_t       size;
    ngx_uint_t   nalloc;
    ngx_pool_t  *pool;
} ngx_array_t;

從記憶體上來看,array是一塊連續的記憶體區域。因此,作為描述陣列的結構體需要:

描述起始地址描述結束地址,此外需要描繪陣列元素的大小以便於索引陣列的每個元素,以及描述記憶體區域已使用的大小

這樣,ngx_array_t的每個成員變數就很容易理解了:

  • elts用來描述陣列使用的記憶體塊的起始地址;
  • size用來描述陣列元素的大小;
  • nalloc用來描述記憶體塊最多能容納的陣列元素個數,因此,記憶體塊的結束地址= elts+nalloc*size
  • nelts用來描述當前記憶體塊已存在的元素個數;
  • pool表示ngx_array_t使用的記憶體所在的記憶體池。

ngx_array_t的管理和使用

ngx_array_t的使用可以從以下幾個方面來分析:

1)ngx_array_t的建立;
2)如何向ngx_array_t新增元素;
3)如何銷燬ngx_array_t

ngx_array_t的建立

因為ngx_array_t使用elts指標來指向ngx_array_t實際使用的記憶體塊,所以,ngx_array_t的建立分成兩部分:

1.ngx_array_t結構體本身的建立;
2.ngx_array_t所管理的記憶體的建立;

在堆上建立ngx_array_t結構體本身,Nginx提供了函式:

ngx_array_t *ngx_array_create(ngx_pool_t *p, ngx_uint_t n, size_t size);

其定義如下:

ngx_array_t *
ngx_array_create(ngx_pool_t *p, ngx_uint_t n, size_t size)
{
    ngx_array_t *a;

    a = ngx_palloc(p, sizeof(ngx_array_t));
    if (a == NULL) {
        return NULL;
    }

    if (ngx_array_init(a, p, n, size) != NGX_OK) {
        return NULL;
    }

    return a;
}

從原始碼可知:在堆上建立ngx_array_t結構體時,同時也建立了其所管理的記憶體。

ngx_array_t結構體本身的建立

兩種方式:在堆上建立、在棧上建立

  • 在堆上建立,需要使用ngx_pool_t來管理記憶體。
    = 在棧上建立,直接建立ngx_array_t區域性變數即可。

ngx_array_t所管理記憶體的建立

ngx_pool_t申請。

ngx_array_t所管理記憶體的建立,Nginx提供了函式:

static ngx_inline ngx_int_t
ngx_array_init(ngx_array_t *array, ngx_pool_t *pool, ngx_uint_t n, size_t size)
{
    /*
     * set "array->nelts" before "array->elts", otherwise MSVC thinks
     * that "array->nelts" may be used without having been initialized
     */

    array->nelts = 0;
    array->size = size;
    array->nalloc = n;
    array->pool = pool;

    array->elts = ngx_palloc(pool, n * size);
    if (array->elts == NULL) {
        return NGX_ERROR;
    }

    return NGX_OK;
}

這個函式很容易看明白。

輸入在堆上或棧上建立的ngx_array_t結構體、申請記憶體使用的ngx_pool_t記憶體池、申請的陣列元素數目、元素的大小。

函式將elts指向申請的記憶體空間首地址。

ngx_array_t新增元素

ngx_array_t新增元素就是對記憶體進行操作。只需要提供elts + nelts * size指標,向其寫入size大小的資料即為新增元素。

函式宣告:

void *ngx_array_push(ngx_array_t *a);

函式定義:

void *
ngx_array_push(ngx_array_t *a)
{
    void        *elt, *new;
    size_t       size;
    ngx_pool_t  *p;
    // 陣列元素超過預設值時發生記憶體重新分配
    if (a->nelts == a->nalloc) {

        /* the array is full */

        size = a->size * a->nalloc;

        p = a->pool;

        if ((u_char *) a->elts + size == p->d.last
            && p->d.last + a->size <= p->d.end)
        {
            /*
             * the array allocation is the last in the pool
             * and there is space for new allocation
             */

            p->d.last += a->size;
            a->nalloc++;

        } else {
            /* allocate a new array */

            new = ngx_palloc(p, 2 * size);
            if (new == NULL) {
                return NULL;
            }
            // 直接將原來的內容拷貝到新記憶體塊中,原來的記憶體沒有重新利用
            ngx_memcpy(new, a->elts, size);
            a->elts = new;
            a->nalloc *= 2;
        }
    }

    elt = (u_char *) a->elts + a->size * a->nelts;
    a->nelts++;

    return elt;
}

呼叫ngx_array_push獲取分配給插入元素的記憶體地址。如果元素個數超過預設值,發生重分配記憶體。原來的記憶體沒有處理,因此會發生浪費。

另外Nginx還提供了ngx_array_push_n這個函式來處理插入n個元素的情況。

可知,ngx_array_pushngx_array_push_nn=1是的特殊情況。他們的程式碼也基本相同。C語言不支援預設值引數,否則,這兩個函式可以合成一個。

ngx_array_t銷燬

根據ngx_array_t建立的分析,可知,ngx_array_t的銷燬其實就是不去使用ngx_array_t

因為,如果在堆上建立ngx_array_t,那麼有ngx_pool_t負責管理記憶體,如果在棧上建立ngx_array_t則變數自動銷燬。

ngx_array_t所管理的記憶體有ngx_pool_t來負責管理。所以,只要不再使用ngx_array_t或者將ngx_array_t指標置空,則ngx_array_t銷燬。

但是Nginx提供了一個用來destory的函式,我們來看看它做了些什麼。

void
ngx_array_destroy(ngx_array_t *a)
{
    ngx_pool_t  *p;

    p = a->pool;
    // 釋放ngx_array_t所管理的記憶體
    if ((u_char *) a->elts + a->size * a->nalloc == p->d.last) {
        p->d.last -= a->size * a->nalloc;
    }
    // 釋放在堆中的ngx_array_t結構體本身
    if ((u_char *) a + sizeof(ngx_array_t) == p->d.last) {
        p->d.last = (u_char *) a;
    }
}

這個函式可能會發生兩種重新回收利用記憶體的情況:

  1. ngx_array_t所管理的記憶體正好是ngx_pool_t最近一次分配的記憶體。
  2. 當堆中的ngx_array_t結構體變數本身正好是ngx_pool_t最近一次分配的記憶體。

所以,在使用完ngx_array_t之後,最好呼叫該函式,雖然它可能什麼都會做,但是也可能進行記憶體池記憶體的重新利用,減少記憶體浪費。

相關文章