返回顶部
首页 > 资讯 > 精选 >nginx内存池如何实现
  • 734
分享到

nginx内存池如何实现

2023-07-02 12:07:16 734人浏览 八月长安
摘要

这篇文章主要讲解了“Nginx内存池如何实现”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“nginx内存池如何实现”吧!一、简介最新稳定版本nginx1.20.2。为了能高效、快速的分配内存

这篇文章主要讲解了“Nginx内存池如何实现”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“nginx内存池如何实现”吧!

一、简介

最新稳定版本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 创建内存池

#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_tngx_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计数

voidngx_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 销毁内存池

  • 回调清理任务

  • 释放大内存

  • 释放内存池本身

voidngx_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; ; p = n, n = n->d.next) {        ngx_free(p);        if (n == NULL) {            break;        }    }}

4.6 大内存释放

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

ngx_int_tngx_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,则回调

voidngx_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

voidngx_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

voidngx_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内存池如何实现这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是编程网,小编将为大家推送更多相关知识点的文章,欢迎关注!

--结束END--

本文标题: nginx内存池如何实现

本文链接: https://lsjlt.com/news/342065.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • nginx内存池如何实现
    这篇文章主要讲解了“nginx内存池如何实现”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“nginx内存池如何实现”吧!一、简介最新稳定版本nginx1.20.2。为了能高效、快速的分配内存...
    99+
    2023-07-02
  • nginx之内存池的实现
    目录一、简介二、数据结构2.1 内存池主要结构2.2 大内存链2.3 清理任务链三、内存结构图3.1 逻辑3.2 实际四、实现4.1 创建内存池4.2 从内存池中分配空间4.3 注册...
    99+
    2024-04-02
  • nginx内存池源码分析
    本篇内容主要讲解“nginx内存池源码分析”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“nginx内存池源码分析”吧!内存池概述    内存池是在真正使用内存之前,...
    99+
    2023-06-25
  • nginx内存池源码解析
    目录内存池概述一、nginx数据结构二、nginx向OS申请空间ngx_create_pool三、nginx向内存池申请空间四、大块内存的分配与释放五、关于小块内存不释放六、销毁和清...
    99+
    2024-04-02
  • C++如何实现定长内存池详解
    目录1. 池化技术2. 内存池概念2.1 内存碎片3. 实现定长内存池3.1 定位new表达式(placement-new)3.2 完整实现总结1. 池化技术 池是在计算机技术中经...
    99+
    2024-04-02
  • C语言如何实现简单的内存池
    本篇内容主要讲解“C语言如何实现简单的内存池”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“C语言如何实现简单的内存池”吧!前言在编程过程中,尤其是对于C语言开发者,其实编程就是在使用内存,不停地...
    99+
    2023-06-20
  • Python内存管理器如何实现池化技术
    目录前言内存层次结构内存管理逻辑内存布局及对应的数据结构内存分配内存释放总结前言 Python 中一切皆对象,这些对象的内存都是在运行时动态地在堆中进行分配的,就连 Python 虚...
    99+
    2024-04-02
  • C++内存池的简单实现
    目录一、内存池基础知识1、什么是内存池1.1 池化技术1.2 内存池2、内存池的作用2.1 效率问题2.2 内存碎片3、内存池技术的演进二、简易内存池原理1、整体设计1.1 内存池结...
    99+
    2024-04-02
  • C++内存池的实现方法
    这篇文章主要讲解了“C++内存池的实现方法”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“C++内存池的实现方法”吧!目录一、内存池基础知识什么是内存池1 池化技术2 内存池内存池的作用1 效...
    99+
    2023-06-20
  • C++内存管理之简易内存池的实现
    目录什么是内存池?它的实现过程为:初步实现使用嵌入指针改进更简化:static allocatormacor for static allocator什么是内存池? 频繁的调用 ma...
    99+
    2024-04-02
  • C++高并发内存池的实现
    目录项目介绍内存池介绍定长内存池的实现高并发内存池整体框架设计threadcachethreadcache整体设计threadcache哈希桶映射对齐规则threadcacheTLS...
    99+
    2024-04-02
  • C++内存管理中简易内存池怎么实现
    这篇文章主要介绍“C++内存管理中简易内存池怎么实现”,在日常操作中,相信很多人在C++内存管理中简易内存池怎么实现问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”C++内存管理中简易内存池怎么实现”的疑惑有所...
    99+
    2023-06-22
  • 详解利用C语言如何实现简单的内存池
    前言 在编程过程中,尤其是对于C语言开发者,其实编程就是在使用内存,不停地变化内存中的数据。当我们想开辟一片新的内存使用时,就会使用malloc实现。但是通过查阅很多资料,发现频繁的...
    99+
    2024-04-02
  • C++中高性能内存池的实现详解
    目录一、概述二、主函数设计三、模板链表栈四、设计内存池五、实现六、与 std::vector 的性能对比总结一、概述 在 C/C++ 中,内存管理是一个非常棘手的问题,我们在编写一个...
    99+
    2022-11-13
    C++高性能内存池 C++ 内存池
  • 使用 C++ 函数的内存分配和销毁来实现内存池
    c++++ 函数的内存分配和销毁可用于实现内存池,从而提高性能。内存池预先分配内存块并重复使用,避免了频繁的系统分配和释放操作。可定义内存分配函数 poolallocate 和内存销毁函...
    99+
    2024-04-22
    c++ 内存池
  • Python内存管理器怎么实现池化技术
    前言Python 中一切皆对象,这些对象的内存都是在运行时动态地在堆中进行分配的,就连 Python 虚拟机使用的栈也是在堆上模拟的。既然一切皆对象,那么在 Python 程序运行过程中对象的创建和释放就很频繁了,而每次都用 malloc(...
    99+
    2023-05-22
    Python
  • C++内存对齐如何实现
    本篇内容介绍了“C++内存对齐如何实现”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!内存对齐的基本原则:结构(struct/class)的内...
    99+
    2023-07-05
  • linux如何实现虚拟内存
    今天小编给大家分享一下linux如何实现虚拟内存的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。虚拟内存的实现需要建立在离散分...
    99+
    2023-07-04
  • 如何解析MySQL线程池内部实现机制
    本篇文章为大家展示了如何解析MySQL线程池内部实现机制,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。摘要在MySQL中,线程池指的是用来管理处理MySQL客户端连...
    99+
    2024-04-02
  • C++中内存池的简单原理及实现详解
    目录为什么要用内存池内存池原理内存池设计内存池实现为什么要用内存池 C++程序默认的内存管理(new,delete,malloc,free)会频繁地在堆上分配和释放内存,导致性能的损...
    99+
    2023-03-01
    C++内存池原理 C++实现内存池 C++内存池
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作