nginx 源码学习(六) 基本数据结构 ngx_array_t
ngx_array_t 介绍 ngx_array_t是nginx内部使用的数组结构。显而易见ngx_array_t是一个顺序容器,它以数组的形式存储元素,并能够在数组容量达到上限时动态扩容数组,很像c++ STL中的vector容器。ngx_array_t 使用了nginx内存池,因此其分配的内存也是在内存池中申请得到的,总的来说ngx_array_t具有访问速度快、数组可动态扩
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
更多推荐
所有评论(0)