转载

Nginx源码分析-ngx_array_t

源文件路径

版本:1.8.0

src/core/Ngx_array.h src/core/Ngx_array.c 

主要作用分析

ngx_array_tNginx 内部使用的数组型数据结构,与 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_pushngx_array_push_nn=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;  } }  

这个函数可能会发生两种重新回收利用内存的情况:

1.当 ngx_array_t 所管理的内存正好是 ngx_pool_t 最近一次分配的内存。

2.当堆中的 ngx_array_t 结构体变量本身正好是 ngx_pool_t 最近一次分配的内存。

所以,在使用完 ngx_array_t 之后,最好调用该函数,虽然它可能什么都会做,但是也可能进行内存池内存的重新利用,减少内存浪费。

正文到此结束
Loading...