原始檔路徑
版本:1.8.0
srccoreNgx_array.h
srccoreNgx_array.c
主要作用分析
ngx_array_t
是Nginx
內部使用的陣列型資料結構,與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_push
是ngx_array_push_n
中n=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;
}
}
這個函式可能會發生兩種重新回收利用記憶體的情況:
- 當
ngx_array_t
所管理的記憶體正好是ngx_pool_t
最近一次分配的記憶體。 - 當堆中的
ngx_array_t
結構體變數本身正好是ngx_pool_t
最近一次分配的記憶體。
所以,在使用完ngx_array_t
之後,最好呼叫該函式,雖然它可能什麼都會做,但是也可能進行記憶體池記憶體的重新利用,減少記憶體浪費。