ngx_array_t 介紹
ngx_array_t是nginx內部使用的數組結構。顯而易見ngx_array_t是一個順序容器,它以數組的形式存儲元素,並能夠在數組容量達到上限時動態擴容
數組,很像c++ STL中的vector容器。ngx_array_t 使用了nginx內存池,因此其分配的內存也是在內存池中申請得到的,總的來說ngx_array_t具有訪問
速度快、數組可動態擴容、負責容器元素內存分配等優點。
ngx_array_t基本結構
nginx數組實現在文件:./src/core/ngx_array.{h,c}。
nginx的數組結構爲ngx_array_t,定義如下:
typedef struct ngx_array_s ngx_array_t;
struct ngx_array_s {
void *elts; //具體的數據區域的起始地址
ngx_uint_t nelts; //已經存儲了的元素數量
size_t size; //單個元素的大小(字節)
ngx_uint_t nalloc; //數組容量,即數組預先分配的內存大小
ngx_pool_t *pool; //內存池,用其保存分配此數組的內存池地址。
};
在32位系統上,sizeof(ngx_array_t)=20B,由定義可見,nginx的數組也要從內存池中分配。將分配nalloc個大小爲size的小空間(分配的大小
爲nalloc * size B)。
ngx_array_t基本操作
下面介紹ngx_array_t 包含的一些基本操作。ngx_array_t*ngx_array_create(ngx_pool_t *p, ngx_uint_t n, size_t size);//創建數組
ngx_array_create作用是創建一個動態數組,並預分配n個大小爲size的內存空間,參數p是內存池,n是初始分配元素的最大個數,size是每個元素
所佔用內存的大小。首先分配數組頭(20B),然後分配數組數據區(nalloc * size B),兩次分配均在傳入的內存池(pool指向的內存池)中進行。
然後簡單初始化數組頭並返回數組頭的起始位置。代碼如下:
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;
}
a->elts = ngx_palloc(p, n * size);//接着分配n*size大小的區域作爲數組數據區
if (a->elts == NULL) {
return NULL;
}
a->nelts = 0;//初始化
a->size = size;
a->nalloc = n;
a->pool = p;
return a;//返回數組頭的起始位置
}
ngx_array_t 對於的邏輯結構如下圖:
static ngx_inlinengx_int_t
ngx_array_init(ngx_array_t*array, ngx_pool_t *p, ngx_uint_t n, size_t size)//初始化數組
初始化1個已經存在的動態數組, 並預分配n個大小爲size的內存空間,參數array爲指向數組結構體指針,p,n,size和ngx_array_create參數意義相同。
代碼如下:
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 beeninitialized
*/
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_create的區別,不同之處在,init不需要爲ngx_array_t本身分配空間。
void ngx_array_destroy(ngx_array_t*a) // 銷燬數組
銷燬已經分配的數組數據區以及數組頭,參數a是指向動態數組的指針。
這裏的銷燬動作實際上就是修改內存池的last指針,並沒有調用free等釋放內存的操作,這種操作效率比較高。代碼如下:
void
ngx_array_destroy(ngx_array_t *a)
{
ngx_pool_t *p;
p = a->pool;
if ((u_char *) a->elts + a->size * a->nalloc == p->d.last) {//先銷燬數組數據區
p->d.last -= a->size * a->nalloc;
}
if ((u_char *) a + sizeof(ngx_array_t) == p->d.last) {//接着銷燬數組頭
p->d.last = (u_char *) a;//設置內存池的last指針
}
}
void * ngx_array_push(ngx_array_t*a) // 添加一個元素
從數組a中取一個存儲一個元素的空間,並返回這個空間地址, 參數a是動態數組結構體指針。可知, nginx數組的用法就是先申請內存,然後我們需要
對返回的指針指向的地址進行賦值等操作來實現實際數組值的添加。代碼如下:
/*
返回可以在該數組數據區中添加這個元素的位置
*/
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//若內存池的last指針指向數組數據區的末尾
&& p->d.last + a->size <= p->d.end)//且內存池未使用的區域可以再分配一個size大小的小空間
{
/*
* the array allocation is the last in the pool
* and there is space for new allocation
*/
p->d.last += a->size;//分配一個size大小的小空間(a->size爲數組一個元素的大小)
a->nalloc++;//實際分配小空間的個數加1
} else {//否則,擴展數組數據區爲原來的2倍
/* 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; //返回該末尾指針,即下一個元素應該存放的位置
}
通過上面的代碼分析,可以不程序流程分爲以下幾種情況:
① 如果array當前已分配的元素個數小於最大分配個數(即a->nelts < a->nalloc),那麼用數組元素首地址a->elts計算出分配元素的首地址,
並返回結果。
② 如果array中當前數組數據區滿(即a->nelts == a->nalloc),並且array所在內存池pool的last指針指向數組數據區的末尾
(即 (u_char *)a->elts + size == p->d.last)且還有空間可分配給新元素(即p->d.last+ a->size <= p->d.end),那麼對array在本對array進行
擴充一個單元,擴充後即變成情形①進行處理。
③ 如果array中當前數組數據區滿(即a->nelts == a->nalloc),且不滿足(u_char *)a->elts + size == p->d.last或p->d.last + a->size <= p->d.end,
那麼對array大小增大一倍後進行重新分配,並將原來array內容拷貝到新地址空間中,完成後最大容量變成原來的兩倍,同情形①進行處理。
void * ngx_array_push_n(ngx_array_t*a, ngx_uint_t n) //添加n個元素
向當前動態數組a添加n個元素,返回的是新添加這批元素中第一個元素的地址, 參數a是動態數組結構體指針。ngx_array_push_n和ngx_array_push
是類似的處理,不再次敷述了。
測試例子
爲了更好的理解上面的知識點,寫一些測試代碼,並進行調試來進一步理解開源代碼的原理和設計思路。
測試代碼如下:// ngx_array_test.c
#include <stdio.h>
#include <string.h>
#include "ngx_config.h"
#include "nginx.h"
#include "ngx_conf_file.h"
#include "ngx_core.h"
#include "ngx_string.h"
#include "ngx_palloc.h"
#include "ngx_array.h"
#define N 5
volatile ngx_cycle_t *ngx_cycle;
void ngx_log_error_core(ngx_uint_t level, ngx_log_t *log,
ngx_err_t err, const char *fmt, ...)
{
}
// 自定義結構體類型
typedef struct node
{
int id;
char buf[32];
}Node;
void print_array(ngx_array_t *a)// 遍歷輸出array
{
printf("-------------------------------\n");
Node *p = a->elts;
size_t i;
for(i=0; i < a->nelts; ++i)
{
printf("%s.\n", (p+i)->buf);
}
printf("-------------------------------\n");
}
int main()
{
ngx_pool_t *pool;
int i;
Node *ptmp;
char str[] = "hello NGX!";
ngx_array_t *a;
pool = ngx_create_pool(1024, NULL);// 創建內存池
printf("Create pool. pool max is %d\n", pool->max);
a = ngx_array_create(pool, N, sizeof(Node));// 創建動態數組
printf("Create array. size=%d nalloc=%d\n", a->size, a->nalloc);
printf("unused memory size is %d\n", (ngx_uint_t)(pool->d.end -
pool->d.last) );
for(i=0; i<8; ++i)
{
ptmp = ngx_array_push(a);// 添加一個元素
ptmp->id = i+1;
sprintf(ptmp->buf, "My Id is %d,%s", ptmp->id, str);
}
ptmp = ngx_array_push_n(a, 2);// 添加兩個元素
ptmp->id = i+1;
sprintf(ptmp->buf, "My Id is %d,%s", ptmp->id, str);
++ptmp;
ptmp->id = i+2;
sprintf(ptmp->buf, "My Id is %d,%s", ptmp->id, str);
print_array(a);
printf("unused memory size is %d\n", (ngx_uint_t)(pool->d.end -
pool->d.last) );
ngx_array_destroy(a);
printf("After destroy array unused memory size is %d\n",
(ngx_uint_t)(pool->d.end - pool->d.last) );
ngx_destroy_pool(pool);
return 0;
}
對於上面的代碼, 編寫 相應的Makefile(不熟悉make的可以 參考這裏)文件如下:
CC=gcc
C_FLAGS = -g -Wall -Wextra
DIR=/home/dane/nginx-1.2.0
TARGETS=ngx_array_test
TARGETS_FILE=$(TARGETS).c
all:$(TARGETS)
clean:
rm -f $(TARGETS) *.o
CORE_INCS=-I $(DIR)/src/core/ \
-I $(DIR)/objs/ \
-I $(DIR)/src/event \
-I $(DIR)/src/event/modules \
-I $(DIR)/src/os/unix \
-I $(DIR)/Nginx_Pre/pcre-8.32/
NGX_OBJ = $(DIR)/objs/src/core/ngx_palloc.o \
$(DIR)/objs/src/core/ngx_string.o \
$(DIR)/objs/src/os/unix/ngx_alloc.o \
$(DIR)/objs/src/core/ngx_array.o
$(TARGETS):$(TARGETS_FILE)
$(CC) $(C_FLAGS) $(TARGETS_FILE) $(CORE_INCS) $(NGX_OBJ) -o $@
上面的Makefile 編寫好後, 直接 make 就可產生 出 可執行文件 ngx_array_test
./ngx_array_test 即可運行 可執行文件。
結果如下:
通過程序分析可知剛開始我們創建一個內存池爲1024字節大小的內存池,除去內存池頭部節點所佔內存大小(40B),此時內存池max
即最大可用內存爲 1024 - 40 = 984 字節,然後ngx_array_create 創建動態數組,此時所內存池大小爲 頭部20字節以及 申請的內存
塊(即數據區n*size大小 ) 5*36=180 字節,創建這樣的動態數組總共消耗內存池大小爲 20+180 = 200 字節,所以此時內存池所剩內
存大小 爲 984 - 200 = 784 字節,隨後在數組中添加第6個元素時,此時數組數據區已滿,需要分配2陪的數據區,因此最後內存池所
剩內存大小爲784-180=604字節。最後調用ngx_array_destroy()通過代碼分析我們需要注意:數組在擴容時,舊的內存不會被釋放,
會造成內存的浪費。因此,最好能提前規劃好數組的容量,在創建或者初始化的時候一次搞定,避免多次擴容,造成內存浪費。
最後內存真正釋放需要通過調用函數ngx_destroy_pool().
參考:
http://code.google.com/p/nginxsrp/wiki/NginxCodeReview
http://blog.csdn.net/daniel_ustc/article/details/11645293
http://blog.csdn.net/livelylittlefish/article/details/6599056