您当前的位置:首页 > 电脑百科 > 站长技术 > 服务器

Nginx 内存池似懂非懂?一文带你看清高性能服务器内存池

时间:2021-08-03 10:09:27  来源:  作者:lee哥的服务器开发

Nginx 内存池 ngx_pool_t

nginx 是自己实现了内存池的,所以在nginx ngx_pool_t 这个结构也随处可见,这里主要分析一下内存池的分配逻辑。

内存池实现了包括小块内存、大块内存和清理资源几种资源的处理,应该来说覆盖了绝大数的使用场景了。

文章相关视频讲解:

高性能服务器为什么需要内存池?内存如何分配? 如何设计内存 ?看视频讲解:「链接」

Nginx源码分析之内存池与线程池:「链接」

相关结构定义

// 大块内存
typedef struct ngx_pool_large_s  ngx_pool_large_t;
struct ngx_pool_large_s {
    ngx_pool_large_t     *next;         // 下一个大块内存池
    void                 *alloc;        // 实际分配内存
};

// 小块内存池
typedef struct {
    u_char               *last;         // 可分配内存起始地址
    u_char               *end;          // 可分配内存结束地址
    ngx_pool_t           *next;         // 指向内存管理结构
    ngx_uint_t            failed;       // 内存分配失败次数
} ngx_pool_data_t;

// 内存池管理结构
typedef struct ngx_pool_s            ngx_pool_t;
struct ngx_pool_s {
    ngx_pool_data_t       d;            // 小块内存池
    size_t                max;          // 小块内存最大的分配内存,评估大内存还是小块内存
    ngx_pool_t           *current;      // 当前开始分配的小块内存池
    ngx_chain_t          *chain;        // chain
    ngx_pool_large_t     *large;        // 大块内存
    ngx_pool_cleanup_t   *cleanup;      // 待清理资源
    ngx_log_t            *log;          // 日志对象
};

ngx_pool_t 是整个内存池的管理结构,这种结构对于个内存池对象来说可能存在多个,但是对于用户而言,第一下访问的始终是创建时返回的那个。多个 ngx_pool_t 通过 d.next 来进行连接,current 指向 当前开始分配的小块内存池,注意 ngx_pool_data_t 在内存池结构的起始处,可以进行类型转换访问到不同的成员。

实现

内存对齐

#define ngx_align(d, a)     (((d) + (a - 1)) & ~(a - 1))
#define ngx_align_ptr(p, a)                                                   
    (u_char *) (((uintptr_t) (p) + ((uintptr_t) a - 1)) & ~((uintptr_t) a - 1))

参考 ngx_align 值对齐宏 分析,ngx_align_ptr 同理

创建内存池

max 的最大值为 4095,当从内存池中申请的内存大小大于 max 时,不会从小块内存中进行分配。

ngx_uint_t  ngx_pagesize = getpagesize();  // linux 上是 4096
#define NGX_POOL_ALIGNMENT 16
#define NGX_MAX_ALLOC_FROM_POOL  (ngx_pagesize - 1)  // 4095

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);  // 16 字节对齐申请 size 大小的内存
    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);               // 设置小块内存可分配的最大值(小于 4095)
    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;
}

内存池创建后的结构逻辑如图所示:

Nginx 内存池似懂非懂?一文带你看清高性能服务器内存池

 

内存申请

申请的内存块以 max 作为区分

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

小块内存申请

current 指向每次申请内存时开始检索分配的小块内存池,而 ngx_palloc_small 的参数 pool 在内存池没有回收时,是固定不变的。

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;  // 从 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); // 没有满足分配的内存池,再申请一个小块内存池
}

当在小块内存池中找到了合适的内存后的结构如下:

Nginx 内存池似懂非懂?一文带你看清高性能服务器内存池

 

当没有小块内存池满足申请时,会再申请一个小块内存池来满足分配,在设置完 last 和 end 两个内存指示器后,对从 current 开始的内存池成员 failed 进行自增操作,并且当这个内存池的 failed 分配次数大于 4 时,表面这个内存分配失败的次数太多,根据经验应该下一次分配可能还是失败,所以直接跳过这个内存池,移动 current。


新的内存块插入至内存池链表的尾端。

#define NGX_ALIGNMENT   sizeof(unsigned long)  // 8

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);  // 16 字节对齐申请
    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;
}

分配一块内存池后逻辑结构如下:

Nginx 内存池似懂非懂?一文带你看清高性能服务器内存池

 

大块内存申请

大块内存是通过 large 连接的,并且都属于 ngx_create_pool 返回的 ngx_pool_t 结构。malloc 分配的内存由一个 ngx_pool_large_t 节点来挂载,而这个 ngx_pool_large_t 节点又是从小块内存池中分配的。

  • 为避免large链表长度过大导致在遍历寻找空闲挂载节点耗时过长,限制了遍历的节点为3,如果没有满足要求则直接分配
  • 头插法 插入至large链表中,新的节点后面也是最先被访问
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);  // 调用 malloc
    if (p == NULL) {
        return NULL;
    }

    n = 0;

    for (large = pool->large; large; large = large->next) {  // 从large 中链表中找到 alloc 为 NULL 的节点,将分配的内存挂在该节点上
        if (large->alloc == NULL) {
            large->alloc = p;
            return p;
        }

        if (n++ > 3) {  // 为了避免过多的遍历,限制次数为 0
            break;
        }
    }

    // 当遍历的 ngx_pool_large_t 节点中 alloc 都有指向的内存时,从小块内存中分配一个 ngx_pool_large_t 节点用于挂载新分配的大内存
    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;
}

第一次大块内存分配后的结构如下:

Nginx 内存池似懂非懂?一文带你看清高性能服务器内存池

 

完整内存池结构逻辑

  • 所有的内存池结构都通过 d.next 连接
  • 前两个内存池结构的 current 都指向第三个内存池结构
  • 所有的 ngx_pool_large_t 节点都是从小内存池中分配的
  • 所有的 ngx_pool_large_t 节点都是连接在首个内存池结构上的
  • ngx_pool_large_t 节点的 alloc 被释放但 ngx_pool_large_t 节点不回收
Nginx 内存池似懂非懂?一文带你看清高性能服务器内存池

 

总结

ngx_pool_t 内存分配方面

  • 通过 current 和 d.next 来访问其他的内存池结构
  • 插入方式
    • 小块内存池通过尾插法插入至内存池链表的尾端
    • 大块内存通过头插法插入至large链表的首部
  • 限制次数
    • 小内存分配失败(failed)次数大于4次后就不再作为分配内存的池子了
    • 大内存只寻找 large 链表中前三节点是否可以挂载新分配的内存
  • 内存对齐,多处内存对齐减少内存跨 cache 的数量

其实总体而言这是一个比较简单的内存池了,还是有一些内存浪费的地方,限制次数 可以说明这个情况,不过这也是在简单、高效和内存分配上的一个平衡了



Tags:内存池   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,如有任何标注错误或版权侵犯请与我们联系(Email:2595517585@qq.com),我们将及时更正、删除,谢谢。
▌相关推荐
一、为什么需要使用内存池在C/C++中我们通常使用malloc,free或new,delete来动态分配内存。一方面,因为这些函数涉及到了系统调用,所以频繁的调用必然会导致程序性能的损耗;另一...【详细内容】
2021-11-17  Tags: 内存池  点击:(37)  评论:(0)  加入收藏
nginx 内存池 ngx_pool_tnginx 是自己实现了内存池的,所以在nginx ngx_pool_t 这个结构也随处可见,这里主要分析一下内存池的分配逻辑。内存池实现了包括小块内存、大块内存和...【详细内容】
2021-08-03  Tags: 内存池  点击:(71)  评论:(0)  加入收藏
内存池是自己向OS请求的一大块内存,自己进行管理。##系统调用## 我们先测试系统调用new/delete的用时。#include <iostream>#include <time.h> using namespace std;class Te...【详细内容】
2019-08-28  Tags: 内存池  点击:(252)  评论:(0)  加入收藏
▌简易百科推荐
阿里云镜像源地址及安装网站地址https://developer.aliyun.com/mirror/centos?spm=a2c6h.13651102.0.0.3e221b111kK44P更新源之前把之前的国外的镜像先备份一下 切换到yumcd...【详细内容】
2021-12-27  干程序那些事    Tags:CentOS7镜像   点击:(1)  评论:(0)  加入收藏
前言在实现TCP长连接功能中,客户端断线重连是一个很常见的问题,当我们使用netty实现断线重连时,是否考虑过如下几个问题: 如何监听到客户端和服务端连接断开 ? 如何实现断线后重...【详细内容】
2021-12-24  程序猿阿嘴  CSDN  Tags:Netty   点击:(12)  评论:(0)  加入收藏
一. 配置yum源在目录 /etc/yum.repos.d/ 下新建文件 google-chrome.repovim /etc/yum.repos.d/google-chrome.repo按i进入编辑模式写入如下内容:[google-chrome]name=googl...【详细内容】
2021-12-23  有云转晴    Tags:chrome   点击:(7)  评论:(0)  加入收藏
一. HTTP gzip压缩,概述 request header中声明Accept-Encoding : gzip,告知服务器客户端接受gzip的数据 response body,同时加入以下header:Content-Encoding: gzip:表明bo...【详细内容】
2021-12-22  java乐园    Tags:gzip压缩   点击:(8)  评论:(0)  加入收藏
yum -y install gcc automake autoconf libtool makeadduser testpasswd testmkdir /tmp/exploitln -s /usr/bin/ping /tmp/exploit/targetexec 3< /tmp/exploit/targetls -...【详细内容】
2021-12-22  SofM    Tags:Centos7   点击:(7)  评论:(0)  加入收藏
Windows操作系统和Linux操作系统有何区别?Windows操作系统:需支付版权费用,(华为云已购买正版版权,在华为云购买云服务器的用户安装系统时无需额外付费),界面化的操作系统对用户使...【详细内容】
2021-12-21  卷毛琴姨    Tags:云服务器   点击:(6)  评论:(0)  加入收藏
参考资料:Hive3.1.2安装指南_厦大数据库实验室博客Hive学习(一) 安装 环境:CentOS 7 + Hadoop3.2 + Hive3.1 - 一个人、一座城 - 博客园1.安装hive1.1下载地址hive镜像路径 ht...【详细内容】
2021-12-20  zebra-08    Tags:Hive   点击:(9)  评论:(0)  加入收藏
以下是服务器安全加固的步骤,本文以腾讯云的CentOS7.7版本为例来介绍,如果你使用的是秘钥登录服务器1-5步骤可以跳过。1、设置复杂密码服务器设置大写、小写、特殊字符、数字...【详细内容】
2021-12-20  网安人    Tags:服务器   点击:(7)  评论:(0)  加入收藏
项目中,遇到了一个问题,就是PDF等文档不能够在线预览,预览时会报错。错误描述浏览器的console中,显示如下错误:nginx代理服务报Mixed Content: The page at ******** was loaded...【详细内容】
2021-12-17  mdong    Tags:Nginx   点击:(7)  评论:(0)  加入收藏
转自: https://kermsite.com/p/wt-ssh/由于格式问题,部分链接、表格可能会失效,若失效请访问原文密码登录 以及 通过密钥实现免密码登录Dec 15, 2021阅读时长: 6 分钟简介Windo...【详细内容】
2021-12-17  LaLiLi    Tags:SSH连接   点击:(16)  评论:(0)  加入收藏
相关文章
最新更新
栏目热门
栏目头条