源文件路径
版本:1.8.0
src\core\Ngx_array.h
src\core\Ngx_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
之后,最好调用该函数,虽然它可能什么都会做,但是也可能进行内存池内存的重新利用,减少内存浪费。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。