nginx之array

一、简介

C语言中的数组大小是固定的,每次扩容基本上都会引起数据的拷贝。nginx实现了一套数组基础组件,使用内存池方式分配空间,减少扩容引发的内存分配以及数据拷贝。

主要涉及ngx_array.h, ngx_array.c

二、 数据结构

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表示分配空间的内存池

2.1 逻辑结构图

在这里插入图片描述

2.2 实际结构图

在这里插入图片描述

三、相关API

3.1 创建数组

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

根据代码可以看出,所有的结构体以及数组本身空间都是从内存池中分配的。

3.2 销毁数组

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

对于数组的销毁,其实就是对内存池的操作,而只有两种情况才能立刻将内存返还给内存池,其他情况都只能等待内存池的销毁而回收内存。

数组分了两部分,数组头结构体和数组本身,只有数组结尾地址/结构体结尾地址是最后下次开始分配的地址,才能立刻将内存返还给内存池。

比如最开始画的那个图就是可以立刻回收的。
回收后如下,原有的那些数据就变成了垃圾数据,并且这些内存可以立刻被重新分配给其他模块使用。
在这里插入图片描述

而如下的图就不能被回收,数组后面的空间已经被其他模块使用了,这样就不连续了,无法回收;而且这种内存结构情况下,如果是插入,并且写入数组元素已经达到上限,则只能重新分配空间,拷贝数据:
在这里插入图片描述

3.3 插入数据

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

和一般的插入函数不同,nginx的数组插入函数并没有传递需要插入的数据,这样提供的API更简单,不要考虑数据的类型,也就不需要考虑类似C++函数重载(或者模板)来实现不同类型参数的插入操作。nginx的数组插入函数只是返回需要插入数据的位置的首地址,需要调用方来写入数据。

当预分配的数组大小写满了,则需要扩容,这里分了两种情况:

  • 数组结尾地址和下次分配地址重合,并且还有空间可以分配一个元素

​ 直接增加一个元素

  • 数组结尾地址和下次分配地址不重合

​ 从内存池中重新分配2倍大小的新数组,将已有数据拷贝到新数组中,涉及数组拷贝

3.4 连续插入n个数据

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

和插入一个元素逻辑一致,只是在扩容2倍时,如果n大于等于数组原始大小,则扩大到2n个元素。

3.5 总结

nginx的数组api相对简单,就这4个api。但还是满满的料,很多细节可以慢慢品。

### Nginx 中 epoll 的工作原理 Nginx 使用 Linux 内核提供的 `epoll` 接口来实现高效的 I/O 多路复用机制。这种模型允许单个线程管理大量并发连接,从而显著提高性能[^1]。 #### epoll 工作流程概述 当 Nginx 启动时,会初始化并注册文件描述符到内核中的 `epoll` 实例上。每当有新的网络请求到达时,操作系统会通知 Nginx 哪些套接字已准备好读取或写入数据。这种方式避免了传统轮询方式带来的高 CPU 占用率问题[^2]。 具体来说,在 Nginx 源码中有一个名为 `ngx_event_epoll_module` 的模块专门用于处理基于 `epoll` 的事件驱动逻辑[^3]。该模块内部维护了一个红黑树结构的数据表,记录着所有正在被监控的 socket 连接状态变化情况。 ```c // 示例代码片段展示如何创建监听端口 ngx_listening_t * ngx_create_listening(ngx_conf_t *cf, struct sockaddr *sockaddr, socklen_t socklen) { ... ls = ngx_array_push(&cf->cycle->listening); ... } ``` 这段 C 语言函数展示了 Nginx 如何设置服务器监听端口的过程的一部分[^4]。 ### 配置 Nginx 使用 Epoll 实际上,默认情况下如果是在支持的操作系统环境下(如Linux),Nginx 就已经自动选择了最优的事件处理器——即对于大多数现代 Linux 发行版而言就是 `epoll` 。因此通常不需要特别指定配置项启用此功能。 不过为了确保使用的是 `epoll` ,可以在编译安装 Nginx 之前确认 configure 脚本选项里包含了 `--with-select_module --with-poll_module` 参数;尽管如此做主要是为了让那些不支持更先进技术的老平台也能正常工作而不是强制切换回旧有的 select 或 poll 方法。 另外值得注意的一点是,虽然可以通过调整 worker_processes 和 worker_connections 等参数间接影响到 `epoll` 表现,但这并不改变底层采用哪种多路复用机制的事实。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值