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请求处理的各个阶段详析
Mar 31 Servers
Nginx设置日志打印post请求参数的方法
Mar 31 Servers
nginx搭建图片服务器的过程详解(root和alias的区别)
Mar 31 Servers
Mac环境Nginx配置和访问本地静态资源的实现
Mar 31 Servers
Nginx进程管理和重载原理详解
Apr 22 Servers
zabbix自定义监控nginx状态实现过程
Nov 01 Servers
Nginx动静分离配置实现与说明
Apr 07 Servers
CentOS7安装GlusterFS集群以及相关配置
Apr 12 Servers
nginx 添加http_stub_status_module模块
May 25 Servers
Nginx报404错误的详细解决方法
Jul 23 Servers
Apache自带的ab压力测试工具的实现
Jul 23 Servers
ubuntu端向日葵键盘输入卡顿问题及解决
Dec 24 Servers
vscode远程免密登入Linux服务器的配置方法
vscode内网访问服务器的方法
云服务器部署 Web 项目的实现步骤
在虚拟机中安装windows server 2008的图文教程
腾讯云服务器部署前后分离项目之前端部署
Jun 28 #Servers
windows系统安装配置nginx环境
Jun 28 #Servers
Python安装及建立虚拟环境的完整步骤
You might like
如何在PHP中进行身份认证
2006/10/09 PHP
PHP清除字符串中所有无用标签的方法
2014/12/01 PHP
smarty实现多级分类的方法
2014/12/05 PHP
Linux系统下php获得系统分区信息的方法
2015/03/30 PHP
php用户登录之cookie信息安全分析
2016/05/13 PHP
网页打开自动最大化的js代码
2012/08/22 Javascript
屏蔽网页右键复制和ctrl+c复制的js代码
2013/01/04 Javascript
改变隐藏的input中value值的方法
2014/03/19 Javascript
javascript委托(Delegate)blur和focus用法实例分析
2015/05/26 Javascript
javascript中错误使用var造成undefined
2016/03/31 Javascript
JavaScript动态检验密码强度的实现方法
2016/11/09 Javascript
基于js实现checkbox批量选中操作
2016/11/22 Javascript
JS限定手机版中图片大小随分辨率自动调整的方法
2016/12/05 Javascript
jQuery插件Echarts实现的双轴图效果示例【附demo源码下载】
2017/03/04 Javascript
微信小程序 页面跳转及数据传递详解
2017/03/14 Javascript
微信小程序实现手指触摸画板
2018/07/09 Javascript
vue 集成jTopo 处理方法
2019/08/07 Javascript
Vue 一键清空表单的实现方法
2020/02/07 Javascript
[17:13]DOTA2 HEROS教学视频教你分分钟做大人-斯拉克
2014/06/13 DOTA
[03:00]2014DOTA2国际邀请赛 Titan淘汰潸然泪下Ohaiyo专访
2014/07/15 DOTA
浅谈python requests 的put, post 请求参数的问题
2019/01/02 Python
Python requests模块实例用法
2019/02/11 Python
python找出列表中大于某个阈值的数据段示例
2019/11/24 Python
django连接mysql数据库及建表操作实例详解
2019/12/10 Python
Pytorch之parameters的使用
2019/12/31 Python
Pandas将列表(List)转换为数据框(Dataframe)
2020/04/24 Python
基于PyTorch中view的用法说明
2021/03/03 Python
利用CSS3实现炫酷的飞机起飞动画
2016/09/17 HTML / CSS
大学生专业个人学习的自我评价
2013/10/26 职场文书
个人委托书格式
2014/04/04 职场文书
自主招生推荐信范文
2014/05/10 职场文书
2014机关党员干部“正风肃纪”思想汇报
2014/09/15 职场文书
坚守艰苦奋斗精神坚决反对享乐主义整改措施
2014/09/17 职场文书
面试自我评价范文
2014/09/17 职场文书
怎样写辞职信
2015/02/27 职场文书
校园之声广播稿
2015/08/18 职场文书