Nginx:数据类型 与 数据结构之 ngx_array

Posted 看,未来

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Nginx:数据类型 与 数据结构之 ngx_array相关的知识,希望对你有一定的参考价值。

基本数据类型封装

typedef intptr_t        ngx_int_t;
typedef uintptr_t       ngx_uint_t;

数据结构

由于nginx对内存分配比 较“吝啬”(只有保证低内存消耗,才可能实现十万甚至百万级别的同时并发连接数),所以这些Nginx数据结构天生都是尽可能少占用内存。

字符串

typedef struct {
    size_t      len;		//字符串的有效长度
    u_char     *data;		//指向字符串起始地址
} ngx_str_t;

我发现它这个源码里面注释是真的少哈。。。

ngx_str_t的data成员指向的并不是普通的字符串,因为这段字符串未必会以’\\0’作 为结尾,所以使用时必须根据长度len来使用data成员。

那到这里有的人就想说了:那这不是更耗内存了吗?怎么还反而少占用内存?

我也想知道为什么呢。有疑惑先留着,看到后面自然就有答案了。

跟字符串相关的方法都要带上长度,不然容易出现越界。


空间配置器

typedef struct {
    u_char               *last;	//当前内存池分配到此处,即下一次分配从此处开始 
    u_char               *end;	//内存池结束位置
    ngx_pool_t           *next;	//内存池里面有很多块内存,这些内存块就是通过该指针连成链表的 
    ngx_uint_t            failed;	//内存池分配失败次数 
} ngx_pool_data_t;


struct ngx_pool_s {
    ngx_pool_data_t       d;		//内存池的数据块 
    size_t                max;	//内存池数据块的最大值
    ngx_pool_t           *current;	//指向当前内存池
    ngx_chain_t          *chain;		//该指针挂接一个ngx_chain_t结构,下篇有,但是这个更重要,先放
    ngx_pool_large_t     *large;		//大块内存链表,即分配空间超过max的内存
    ngx_pool_cleanup_t   *cleanup;	//释放内存池的callback
    ngx_log_t            *log;		//日志信息
};

数组

typedef struct {
    void        *elts;		//指向数组的起始地址
    ngx_uint_t   nelts;		//表示数组中已经使用元素数量
    size_t       size;		//限制每个数组元素占用空间大小,也就是用户要存储的一个数据所 占用的字节数必须小于或等于size。
    ngx_uint_t   nalloc;	//最多可存储数据数
    ngx_pool_t  *pool;		//管理内存分配的内存池对象
} ngx_array_t;
//就字面意思呗,也没啥好看的,就普通的初始化空间。
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;
}

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;
}

创建数组后内存池的物理结构图如下:

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;
    }
}

实际的添加操作并不在这两个函数中完成,例如ngx_array_push返回可以在该数组数据区中添加这个元素的位置,ngx_array_push_n则返回可以在该数组数据区中添加n个元素的起始位置,而添加操作即在获得添加位置之后进行

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;	//这是干嘛,有点猛啊,哦,size是局部变量

        p = a->pool;

        if ((u_char *) a->elts + size == p->d.last
            && p->d.last + a->size <= p->d.end)		//还没弹尽粮绝呢?那也不对啊,怎么是小等于,那不就会山穷水尽吗
        {																			//除非,这个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);	//否则,扩展数组数据区为原来的2倍
            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;	//返回该末尾指针,即下一个元素应该存放的位置
}


void *ngx_array_push_n(ngx_array_t *a, ngx_uint_t n)
{
    void        *elt, *new;
    size_t       size;
    ngx_uint_t   nalloc;
    ngx_pool_t  *p;

    size = n * a->size;

    if (a->nelts + n > a->nalloc) {

        /* the array is full */

        p = a->pool;

        if ((u_char *) a->elts + a->size * a->nalloc == p->d.last
            && p->d.last + size <= p->d.end)
        {
            /*
             * the array allocation is the last in the pool
             * and there is space for new allocation
             */

            p->d.last += size;
            a->nalloc += n;

        } else {
            /* allocate a new array */

            nalloc = 2 * ((n >= a->nalloc) ? n : a->nalloc);

            new = ngx_palloc(p, nalloc * a->size);
            if (new == NULL) {
                return NULL;
            }

            ngx_memcpy(new, a->elts, a->nelts * a->size);
            a->elts = new;
            a->nalloc = nalloc;
        }
    }

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

    return elt;
}

向数组中添加元素实际上也是在修该内存池的last指针(若数组数据区满)及数组头信息,即使数组满了,需要扩展数据区内容,也只需要内存拷贝完成,并不需要数据的移动操作,这个效率也是相当高的。


太多了慢慢来,一次太多 hold 不住。

以上是关于Nginx:数据类型 与 数据结构之 ngx_array的主要内容,如果未能解决你的问题,请参考以下文章

MySQL数据类型操作(char与varchar)

nginx之keepalive与pipeline

Nginx之配置文件的解析

Nginx-rtmp之 AMF0 的处理

lua源码分析之string类型的实现

Nginx 之 location 指令匹配规则