资讯专栏INFORMATION COLUMN

Nginx 源码分析:ngx_array_t

zhonghanwen / 2985人阅读

摘要:源文件路径版本主要作用分析是内部使用的数组型数据结构,与语言内置的数组概念上类似,但是有两点主要区别使用内存池来管理内存虽然有预设数组大小的概念,但是在数组元素超出预设值大小时,会在内存池中发生重分配。

源文件路径

版本:1.8.0

srccoreNgx_array.h
srccoreNgx_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;
    }
}

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

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

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

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

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/39162.html

相关文章

  • Nginx 源码分析:ngx_list_t

    摘要:源码路径版本主要作用分析是对通常的这种数据结构重复的造轮子。链表使用的内存池。在堆上创建调用函数与的分析类似,调用该函数会自动向申请内存空间。 源码路径 版本:1.8.0 srccoreNgx_list.h srccoreNgx_list.c 主要作用分析 ngx_list_t是Nginx对通常的list这种数据结构重复的造轮子。 在本篇中,我们先来分析Nginx是如何造这...

    Kahn 评论0 收藏0
  • Nginx 源码分析第三篇之 ngx_queue 队列

    摘要:相关系列前面分析了数组,现在看一下队列和哈希表的实现。队列是一个双向链表,实现了一个队列的操作逻辑。它们都将链表节点塞入数据结构。对于常用的解决冲突的方法有线性探测二次探测和开链法等。 相关系列:http://www.codefrom.com/p/nginx 前面分析了ngx_array_t数组,现在看一下ngx_queue队列和ngx_hash哈希表的实现。 ngx_qu...

    frontoldman 评论0 收藏0
  • 不再依靠巧合编写 Nginx 配置

    摘要:找到这个模块的指令后,则会调用这个指令的解析回调函数即结构体的第三个参数来进行处理。调用他们上面提到的中的回调函数来申请和初始化对应模块的配置结构体。需要注意的是,即时当前是直接在块级别,这三个回调函数都会被调用。拒绝暴力枚举式编写配置文件 原博:https://blog.coordinate35.cn/... 热身 首先来看下这几个小例子: 第一个例子: server { l...

    tulayang 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<