Nginx底层设计与源码分析
上QQ阅读APP看书,第一时间看更新

4.2 数组

数组是每种高级编程语言都有的数据类型。在C语言中定义一个数组时,我们必须知道所存储数据的类型和数量。在PHP等弱类型的语言中,数组可以存储任意类型、任意数量的数据。Nginx的数组占用内存池上一块连续的空间,它存放的数据类型是确定的,而且容量可以扩充,这样的数组更为易用。

Nginx数组的定义如下:

typedef struct {
    void        *elts;
    ngx_uint_t   nelts;
    size_t       size;
    ngx_uint_t   nalloc;
    ngx_pool_t  *pool;
} ngx_array_t;

代码中的参数说明如下。

  • elts:数据块,指向实际存储的数据。
  • nelts:当前数组中已存放数据的数量。
  • size:每个数据的大小。
  • nalloc:已经分配的区域大小,即当前数组可存储数据的数量。
  • pool:存储当前数组的内存池。

Nginx用ngx_array_create函数来创建数组,它的参数有3个,分别是内存池地址、数组初始个数、每个数据的大小。ngx_array_create函数定义如下:

ngx_array_t *ngx_array_create(ngx_pool_t *p, ngx_uint_t n, size_t size);

它首先从内存池中分配数组结构体所需要的空间,并对结构体进行初始化。结构体初始化时会再从内存池中分配n×size的空间,并将返回的地址赋值给elts参数。结构体初始化完成后,由于还没有插入数据,nelts参数的值为0,nalloc和size参数的值为函数参数入参值。初始化函数返回内存池中为数组分配的内存起始位置。

初始化完成之后,Nginx用ngx_array_push函数向数组添加数据。ngx_array_push函数定义如下:

void *ngx_array_push(ngx_array_t *a);

注意

Nginx数组插入数据并不是直接将数据添加到数组中,而是返回数据所需要的内存大小,由用户自定义该内存区域的值。

数组插入数据时,如果数组的容量和已分配的数量不相等,表示内存池中有足够的空间供数据存放,此时直接返回数据所需要的内存空间,并将数组已使用的个数加1,代码如下:

elt = (u_char *) a->elts + a->size * a->nelts;
a->nelts++;

如果数组已分配的数量等于数组的容量,表示此时数组已经存满。当数据存满数组时,一般的做法是开辟一块较大的新内存空间,将当前数据全部赋值到新内存地址。由于Nginx的数组容量是在内存池上分配的,因此不一定需要新开辟空间,这需要依据内存池是否有新的可用空间来确定。

if ((u_char *) a->elts + size == p->d.last && p->d.last + a->size <= p->d.end)
// 内存池当前节点上仍有剩余空间存放数组新数据
{
    p->d.last += a->size;
    a->nalloc++;
} else {
    new = ngx_palloc(p, 2 * size);  // 当内存池地址不够用时,需要新申请内存池。申请内存池
                                    // 的大小是原数组大小的2倍
    if (new == NULL) {
        return NULL;
    }
    ngx_memcpy(new, a->elts, size);// 内存池初始化之后,将原数组依次赋值到新地址上
    a->elts = new;
    a->nalloc *= 2;
}

从上述代码可以看出:当存储数组的内存池有剩余空间时,插入数据直接在当前内存池上向后扩容;当存储数组的内存池空间不足时,需要开辟新的内存空间并将数组数据依次赋值到新内存地址上,以此来保证数组数据的连续性。

当数组使用完毕释放时,直接释放内存池上的空间即可,不用将内存交还给操作系统,从而保证申请和释放内存的高效性。被释放的内存仍可以另作他用,实现内存的重复利用,减少了系统开销。