nginx之内存池的实现


Posted in Servers onJune 28, 2022

一、简介

最新稳定版本nginx1.20.2。
为了能高效、快速的分配内存,以及减少内存碎片等,nginx实现了自己的内存池基础组件。
主要实现文件ngx_palloc.h, ngx_palloc.c

二、数据结构

2.1 内存池主要结构

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_pool_large_t     *large;
    ngx_pool_cleanup_t   *cleanup;
    ngx_log_t            *log;
};

内存池中第一个成员是一个结构体:
使用ngx_pool_data_t结构体来表示当前内存池信息。
last :下次开始分配的地址
end: 内存池的结束地址
next: 内存池链表,将多个内存池连接起来

max
整个内存池的最大大小

current
指向从当前内存池开始查找可用内存

chain
buffer使用的,这里不涉及

large
当需要的内存大于内存池最大大小时,需要通过malloc直接分配,然后形成链表进行组织

cleanup
清理工作的回调链表

log
日志句柄

2.2 大内存链

当需要分配的内存比内存池的最大大小都大时,内存池无法满足分配,所以直接从系统中分配,然后构成一个链表进行维护。

typedef struct ngx_pool_large_s  ngx_pool_large_t;

struct ngx_pool_large_s {
    ngx_pool_large_t     *next;
    void                 *alloc;
};

2.3 清理任务链

有一个回调任务的链表,当内存池销毁时,将依次遍历此链表,逐一回调handler进行清理工作。

typedef void (*ngx_pool_cleanup_pt)(void *data);

typedef struct ngx_pool_cleanup_s  ngx_pool_cleanup_t;

struct ngx_pool_cleanup_s {
    ngx_pool_cleanup_pt   handler;
    void                 *data;
    ngx_pool_cleanup_t   *next;
};

三、内存结构图

3.1 逻辑

nginx之内存池的实现

3.2 实际

nginx之内存池的实现

可以看出,很多节点都是从内存池中分配的,所以可以把精力都放在实际的数据上而不必在意其他细节上。

四、实现

4.1 创建内存池

/*
 * NGX_MAX_ALLOC_FROM_POOL should be (ngx_pagesize - 1), i.e. 4095 on x86.
 * On Windows NT it decreases a number of locked pages in a kernel.
 */
#define NGX_MAX_ALLOC_FROM_POOL  (ngx_pagesize - 1)

#define NGX_DEFAULT_POOL_SIZE    (16 * 1024)
ngx_pool_t *
ngx_create_pool(size_t size, ngx_log_t *log)
{
    ngx_pool_t  *p;

    p = ngx_memalign(NGX_POOL_ALIGNMENT, size, log);
    if (p == NULL) {
        return NULL;
    }

    p->d.last = (u_char *) p + sizeof(ngx_pool_t);
    p->d.end = (u_char *) p + size;
    p->d.next = NULL;
    p->d.failed = 0;

    size = size - sizeof(ngx_pool_t);
    p->max = (size < NGX_MAX_ALLOC_FROM_POOL) ? size : NGX_MAX_ALLOC_FROM_POOL;

    p->current = p;
    p->chain = NULL;
    p->large = NULL;
    p->cleanup = NULL;
    p->log = log;

    return p;
}

从代码中可以看到,内存池最大不超过pagesize的大小

nginx之内存池的实现

4.2 从内存池中分配空间

分配函数分了内存对齐和内存不对齐,但这只控制了内存池中分配空间,不控制大内存分配。

(1)分配小空间

  • 内存对齐 ngx_palloc
  • 内存不对齐 ngx_pnalloc
void *
ngx_palloc(ngx_pool_t *pool, size_t size)
{
#if !(NGX_DEBUG_PALLOC)
    if (size <= pool->max) {
        return ngx_palloc_small(pool, size, 1);
    }
#endif

    return ngx_palloc_large(pool, size);
}

当需要分配的空间小于max时,将使用小内存分配方式(即从内存池中分配空间),而ngx_pnalloc和ngx_palloc相比只是调用ngx_palloc_small时的最后一个参数为0。

从pool->current指向的内存池开始遍历,寻找满足分配大小的空间,找到则返回首地址

static ngx_inline void *
ngx_palloc_small(ngx_pool_t *pool, size_t size, ngx_uint_t align)
{
    u_char      *m;
    ngx_pool_t  *p;

    p = pool->current;

    do {
        m = p->d.last;

        if (align) {
            m = ngx_align_ptr(m, NGX_ALIGNMENT);
        }

        if ((size_t) (p->d.end - m) >= size) {
            p->d.last = m + size;

            return m;
        }

        p = p->d.next;

    } while (p);

    return ngx_palloc_block(pool, size);
}

当现有内存池中都无法满足分配条件时,创建新的内存池

static void *
ngx_palloc_block(ngx_pool_t *pool, size_t size)
{
    u_char      *m;
    size_t       psize;
    ngx_pool_t  *p, *new;

    psize = (size_t) (pool->d.end - (u_char *) pool);

    m = ngx_memalign(NGX_POOL_ALIGNMENT, psize, pool->log);
    if (m == NULL) {
        return NULL;
    }

    new = (ngx_pool_t *) m;

    new->d.end = m + psize;
    new->d.next = NULL;
    new->d.failed = 0;

    m += sizeof(ngx_pool_data_t);
    m = ngx_align_ptr(m, NGX_ALIGNMENT);
    new->d.last = m + size;

    for (p = pool->current; p->d.next; p = p->d.next) {
        if (p->d.failed++ > 4) {
            pool->current = p->d.next;
        }
    }

    p->d.next = new;

    return m;
}

其中,创建好新的内存池后,又做了一次遍历,将failed计数加一,当大于4时,将跳过此内存池,下次就不从它开始查找。
即认为超过4次你都不能满足分配,以后都不能满足分配,不再用你了,减少遍历个数,加快成功分配效率

(2)分配大空间

static void *
ngx_palloc_large(ngx_pool_t *pool, size_t size)
{
    void              *p;
    ngx_uint_t         n;
    ngx_pool_large_t  *large;

    p = ngx_alloc(size, pool->log);
    if (p == NULL) {
        return NULL;
    }

    n = 0;

    for (large = pool->large; large; large = large->next) {
        if (large->alloc == NULL) {
            large->alloc = p;
            return p;
        }

        if (n++ > 3) {
            break;
        }
    }

    large = ngx_palloc_small(pool, sizeof(ngx_pool_large_t), 1);
    if (large == NULL) {
        ngx_free(p);
        return NULL;
    }

    large->alloc = p;
    large->next = pool->large;
    pool->large = large;

    return p;
}

可以看出,为了避免分配空间,遍历large链查找可重用的节点,但是如果链表过大又可能太慢,所以只查找前三个,如果三个都没有找到,则直接分配(而且节点也是从内存池中分配的,所以后续清理时,不需要管节点,只需要释放申请的大内存本身)

内存对齐

void *
ngx_pmemalign(ngx_pool_t *pool, size_t size, size_t alignment)
{
    void              *p;
    ngx_pool_large_t  *large;

    p = ngx_memalign(alignment, size, pool->log);
    if (p == NULL) {
        return NULL;
    }

    large = ngx_palloc_small(pool, sizeof(ngx_pool_large_t), 1);
    if (large == NULL) {
        ngx_free(p);
        return NULL;
    }

    large->alloc = p;
    large->next = pool->large;
    pool->large = large;

    return p;
}

4.3 注册清理任务

ngx_pool_cleanup_t *
ngx_pool_cleanup_add(ngx_pool_t *p, size_t size)
{
    ngx_pool_cleanup_t  *c;

    c = ngx_palloc(p, sizeof(ngx_pool_cleanup_t));
    if (c == NULL) {
        return NULL;
    }

    if (size) {
        c->data = ngx_palloc(p, size);
        if (c->data == NULL) {
            return NULL;
        }

    } else {
        c->data = NULL;
    }

    c->handler = NULL;
    c->next = p->cleanup;

    p->cleanup = c;

    ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, p->log, 0, "add cleanup: %p", c);

    return c;
}

可以看出,这里只是分配了一个节点,并没有设置handler以及data数据,所以还得看具体的调用方进行设置,因为这里返回了分配的节点。

比如在函数ngx_create_temp_file

ngx_int_t
ngx_create_temp_file(ngx_file_t *file, ngx_path_t *path, ngx_pool_t *pool,
    ngx_uint_t persistent, ngx_uint_t clean, ngx_uint_t access)
{
    ...

    cln = ngx_pool_cleanup_add(pool, sizeof(ngx_pool_cleanup_file_t));
    if (cln == NULL) {
        return NGX_ERROR;
    }

       ...
        file->fd = ngx_open_tempfile(file->name.data, persistent, access);
				...
        if (file->fd != NGX_INVALID_FILE) {

            cln->handler = clean ? ngx_pool_delete_file : ngx_pool_cleanup_file;
            clnf = cln->data;

            clnf->fd = file->fd;
            clnf->name = file->name.data;
            clnf->log = pool->log;

            return NGX_OK;
        }
			...
}

生成临时文件,将fd以及文件名注册到清理任务中,后续文件不使用了则不需要特殊处理,内存内存池释放时将统一清理。

4.4 重置内存池

  • 释放大内存
  • 重置内存中last
  • 重置failed计数
void
ngx_reset_pool(ngx_pool_t *pool)
{
    ngx_pool_t        *p;
    ngx_pool_large_t  *l;

    for (l = pool->large; l; l = l->next) {
        if (l->alloc) {
            ngx_free(l->alloc);
        }
    }

    for (p = pool; p; p = p->d.next) {
        p->d.last = (u_char *) p + sizeof(ngx_pool_t);
        p->d.failed = 0;
    }

    pool->current = pool;
    pool->chain = NULL;
    pool->large = NULL;
}

这里有个现象:
在内存池中空间不足时,将调用ngx_palloc_block创建一个新的内存池,而last指向的是m += sizeof(ngx_pool_data_t);, 因此当前新分配的内存池将比第一个内存池可用大小多了(max,current,chain,large,cleanup,log)这几个字段大小(可能没有那么多,因为要对齐,可能对齐后就完全一样了),而现在重置时,p->d.last = (u_char *) p + sizeof(ngx_pool_t);每个内存池可用大小又变成一样的。

4.5 销毁内存池

  • 回调清理任务
  • 释放大内存
  • 释放内存池本身
void
ngx_destroy_pool(ngx_pool_t *pool)
{
    ngx_pool_t          *p, *n;
    ngx_pool_large_t    *l;
    ngx_pool_cleanup_t  *c;

    for (c = pool->cleanup; c; c = c->next) {
        if (c->handler) {
            ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, pool->log, 0,
                           "run cleanup: %p", c);
            c->handler(c->data);
        }
    }


    for (l = pool->large; l; l = l->next) {
        if (l->alloc) {
            ngx_free(l->alloc);
        }
    }

    for (p = pool, n = pool->d.next; /* void */; p = n, n = n->d.next) {
        ngx_free(p);

        if (n == NULL) {
            break;
        }
    }
}

4.6 大内存释放

通过遍历找到要释放的节点,将内存释放,并且将alloc设置成NULL,则有了节点重用的情况。

ngx_int_t
ngx_pfree(ngx_pool_t *pool, void *p)
{
    ngx_pool_large_t  *l;

    for (l = pool->large; l; l = l->next) {
        if (p == l->alloc) {
            ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, pool->log, 0,
                           "free: %p", l->alloc);
            ngx_free(l->alloc);
            l->alloc = NULL;

            return NGX_OK;
        }
    }

    return NGX_DECLINED;
}

4.7 分配并清空数据

void *
ngx_pcalloc(ngx_pool_t *pool, size_t size)
{
    void *p;

    p = ngx_palloc(pool, size);
    if (p) {
        ngx_memzero(p, size);
    }

    return p;
}

正常分配的空间中都是垃圾数据,所以当前函数在分配空间后,将分配的空间清零。

4.8 回调文件清理

(1) 手动关闭指定fd

遍历清理任务,找到ngx_pool_cleanup_file的handler,如果是要关闭的fd,则回调

void
ngx_pool_run_cleanup_file(ngx_pool_t *p, ngx_fd_t fd)
{
    ngx_pool_cleanup_t       *c;
    ngx_pool_cleanup_file_t  *cf;

    for (c = p->cleanup; c; c = c->next) {
        if (c->handler == ngx_pool_cleanup_file) {

            cf = c->data;

            if (cf->fd == fd) {
                c->handler(cf);
                c->handler = NULL;
                return;
            }
        }
    }
}

(2) 关闭fd

void
ngx_pool_cleanup_file(void *data)
{
    ngx_pool_cleanup_file_t  *c = data;

    ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, c->log, 0, "file cleanup: fd:%d",
                   c->fd);

    if (ngx_close_file(c->fd) == NGX_FILE_ERROR) {
        ngx_log_error(NGX_LOG_ALERT, c->log, ngx_errno,
                      ngx_close_file_n " \"%s\" failed", c->name);
    }
}

(3) 删除文件并关闭fd

void
ngx_pool_delete_file(void *data)
{
    ngx_pool_cleanup_file_t  *c = data;

    ngx_err_t  err;

    ngx_log_debug2(NGX_LOG_DEBUG_ALLOC, c->log, 0, "file cleanup: fd:%d %s",
                   c->fd, c->name);

    if (ngx_delete_file(c->name) == NGX_FILE_ERROR) {
        err = ngx_errno;

        if (err != NGX_ENOENT) {
            ngx_log_error(NGX_LOG_CRIT, c->log, err,
                          ngx_delete_file_n " \"%s\" failed", c->name);
        }
    }

    if (ngx_close_file(c->fd) == NGX_FILE_ERROR) {
        ngx_log_error(NGX_LOG_ALERT, c->log, ngx_errno,
                      ngx_close_file_n " \"%s\" failed", c->name);
    }
}

到此这篇关于nginx之内存池的实现的文章就介绍到这了,更多相关nginx 内存池内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!


Tags in this post...

Servers 相关文章推荐
nginx如何将http访问的网站改成https访问
Mar 31 Servers
Nginx反爬虫策略,防止UA抓取网站
Mar 31 Servers
Nginx安装完成没有生成sbin目录的解决方法
Mar 31 Servers
nginx作grpc的反向代理踩坑总结
Jul 07 Servers
Consul在linux环境的集群部署
Apr 08 Servers
Nginx配置根据url参数重定向
Apr 11 Servers
nginx日志格式分析和修改
Apr 28 Servers
Windows Server 2019 域控制器安装图文教程
Apr 28 Servers
聊聊配置 Nginx 访问与错误日志的问题
May 25 Servers
关于windows server 2012 DC 环境 重启后蓝屏代码:0xc00002e2的问题
May 25 Servers
Linux磁盘管理方法介绍
Jun 01 Servers
安装harbor作为docker镜像仓库的问题
Jun 14 Servers
vscode远程免密登入Linux服务器的配置方法
vscode内网访问服务器的方法
云服务器部署 Web 项目的实现步骤
在虚拟机中安装windows server 2008的图文教程
腾讯云服务器部署前后分离项目之前端部署
Jun 28 #Servers
windows系统安装配置nginx环境
Jun 28 #Servers
Python安装及建立虚拟环境的完整步骤
You might like
在IIS上安装PHP4.0正式版
2006/10/09 PHP
php入门教程 精简版
2009/12/13 PHP
jQuery+PHP+ajax实现微博加载更多内容列表功能
2014/06/27 PHP
详解WordPress开发中get_header()获取头部函数的用法
2016/01/08 PHP
showModalDialog 和 showModelessDialog
2007/01/22 Javascript
juqery 学习之三 选择器 子元素与表单
2010/11/25 Javascript
JS 进度条效果实现代码整理
2011/05/21 Javascript
JS 对输入框进行限制(常用的都有)
2013/07/30 Javascript
jQuery搜索同辈元素方法
2015/02/10 Javascript
JavaScript实现给按钮加上双重动作的方法
2015/08/14 Javascript
jsTree使用记录实例
2016/12/01 Javascript
Node.js中如何合并两个复杂对象详解
2016/12/31 Javascript
JS实现左边列表移到到右边列表功能
2018/03/28 Javascript
解决Layui数据表格中checkbox位置不居中的方法
2018/08/15 Javascript
Node.js console控制台简单用法分析
2019/01/04 Javascript
实例讲解JavaScript预编译流程
2019/01/24 Javascript
JavaScript React如何修改默认端口号方法详解
2020/07/28 Javascript
[01:00:10]完美世界DOTA2联赛PWL S2 FTD vs Inki 第二场 11.21
2020/11/24 DOTA
Python greenlet实现原理和使用示例
2014/09/24 Python
python3+requests接口自动化session操作方法
2018/10/13 Python
python实现指定字符串补全空格、前面填充0的方法
2018/11/16 Python
关于Pycharm无法debug问题的总结
2019/01/19 Python
解决python测试opencv时imread导致的错误问题
2019/01/26 Python
Python爬虫beautifulsoup4常用的解析方法总结
2019/02/25 Python
keras中epoch,batch,loss,val_loss用法说明
2020/07/02 Python
Python 调用C++封装的进一步探索交流
2021/03/04 Python
html5指南-2.如何操作document metadata
2013/01/07 HTML / CSS
澳大利亚小众服装品牌:Maurie & Eve
2018/03/27 全球购物
西班牙宠物用品和食品网上商店:Tiendanimal
2019/06/06 全球购物
巴西本土电商平台:Americanas
2020/06/21 全球购物
自动化专业个人求职信范文
2013/12/30 职场文书
本科毕业生求职自荐信
2014/02/03 职场文书
求职简历自我评价范例
2014/03/12 职场文书
小学生五年级大队长竞选发言稿
2014/09/12 职场文书
选择比努力更重要?这是长期以来对“努力”的最大误解
2019/07/12 职场文书
volatile保证可见性及重排序方法
2022/08/05 Java/Android