nginx 源码学习(六) 基本数据结构 ngx_array_t
2014-02-14 18:38
706 查看
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
相关文章推荐
- nginx 源码学习(五) 基本数据结构 ngx_list_t
- Nginx 源码分析-- ngx_array、ngx_list基本数据结构
- nginx 源码学习(四) 基本数据结构 ngx_queue_t
- nginx 源码学习笔记(十)——基本容器——ngx_hash
- nginx 源码学习笔记(十一)——基本容器——ngx_list
- nginx 源码学习笔记(十一)——基本容器——ngx_list
- nginx 源码学习笔记(十一)——基本容器——ngx_list
- nginx 源码学习笔记(十二)——基本容器——ngx_buf
- nginx 源码学习笔记(十二)——基本容器——ngx_buf
- Nginx基本数据结构之ngx_array_t
- nginx 源码学习笔记(十二)——基本容器——ngx_buf
- nginx 源码学习笔记(八)——基本容器——array数组
- nginx 源码学习笔记(十)——基本容器——ngx_hash
- nginx 源码学习笔记(八)——基本容器——array数组
- nginx 源码学习笔记(六)——nginx基本数据结构
- nginx源码学习(一)-基本数据结构
- nginx 源码学习笔记(六)——nginx基本数据结构
- nginx源码初读(5)--让烦恼从数据结构开始(ngx_array)
- nginx 源码学习笔记(六)——nginx基本数据结构
- nginx 源码学习笔记(十)——基本容器——ngx_hash