Nginx源码:内存池的实现

news2025/8/4 1:18:53

文章目录

    • 1、数据结构
    • 2、接口函数
      • 2.1、创建内存池
      • 2.2、内存分配
        • 2.2.1、小块内存分配
        • 2.2.2、大块内存分配
      • 2.3、内存释放
        • 2.3.1、大块内存释放
        • 2.3.2、内存池释放
    • 4、参考

为什么需要对内存管理?

  • 避免频繁的系统调用带来的开销。
  • 减少了频繁分配和释放小块内存产生的内存碎片。

解决上述问题,最好的方法就是内存池。内存池就是对堆上的内存进行管理。

内存池的具体做法是固定大小、提前申请、重复利用。

  • 固定大小。在调用内存分配函数的时候,小块内存每次都分配固定大小的内存块,这样避免了内存碎片产生的可能性。
  • 提前申请一块大的内存,内存不够用时再二次分配,减少了malloc的次数,提高了效率。

Nginx 使用内存池管理进程内存,当接收到请求时,创建一个内存池。处理请求过程中需要的内存都从这个内存池中申请,请求处理完成后释放内存池。Nginx 将内存池中的内存分为两类:小块内存和大块内存。对于小块内存,用户申请后并不需要释放,而是等待释放内存池时再释放。对于大块内存,用户可以调用相关接口进行释放,也可以等内存池释放时再释放。同时 Nginx 内存池支持增加回调函数,当内存池释放时,自动调用回调函数释放用户申请的资源。回调函数允许增加多个,通过链表进行链接,在内存池释放时被逐一调用。

源码位置:src/core/ngx_palloc.h, src/core/ngx_palloc.c

1、数据结构

在这里插入图片描述

内存池由内存块链表(内存池节点)组成,每个内存块分为两个两部分,一部分存储该内存块相关信息,另一部分用于小块内存的分配。

ngx_pool_data_t

typedef struct {
    u_char               *last;   // 指向该内存块已分配内存的末尾地址,下一个待分配内存的起始地址
    u_char               *end;    // 指向该内存块的末尾地址
    ngx_pool_t           *next;   // 指向下一个内存块
    ngx_uint_t            failed; // 当前内存块分配空间失败的次数
} ngx_pool_data_t;

ngx_pool_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_large_s:大块内存链表节点

struct ngx_pool_large_s {
    ngx_pool_large_t     *next;     // 指向下一个大块内存节点
    void                 *alloc;    // 指向实际分配的大块内存
};

2、接口函数

2.1、创建内存池

申请的内存由两部分组成,一部分用来容纳ngx_pool_t结构体,另一部分内存则是用于满足用户申请。ngx_pool_data_t结构体中的last指针和end指针之间的内存是空闲的,当用户申请小块内存时,如果空闲的内存大小满足用户的需求,则可以分配给用户。

// 内存对齐,默认16字节对齐
#define NGX_POOL_ALIGNMENT       16

/**
 * @brief 创建内存池
 * @param size 内存块的大小
 * @param log  log 打印日志
 * @return ngx_pool_t* 返回创建的内存池地址
 */
ngx_pool_t *ngx_create_pool(size_t size, ngx_log_t *log) {
    ngx_pool_t  *p;

    // 申请内存,默认16字节对齐
    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;
}

2.2、内存分配

用户可以调用ngx_palloc向内存池申请未初始化的内存。分配内存的时候,根据本次申请空间的大小 size 和内存池设定的pool->max,判断当前要分配的内存是小块内存还是大块内存。

void *ngx_palloc(ngx_pool_t *pool, size_t size) {
    // 判断申请的内存块是大块还是小块
    // 1、申请小块内存
    if (size <= pool->max) {
        return ngx_palloc_small(pool, size, 1);
    }
    // 2、申请大块内存
    return ngx_palloc_large(pool, size);
}

2.2.1、小块内存分配

若用户申请的是小块内存,则调用ngx_palloc_small遍历内存池的内存块,寻找其中是否有满足需求的内存块

  • 有可分配的内存块,返回待分配空间的首地址
  • 没有可分配的内存块,创建一个新的内存池节点
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);
        }

        // 当前内存块的剩余空间是否足够分配
        // 1、够分配
        if ((size_t) (p->d.end - m) >= size) {
            // 更新 last 指针
            p->d.last = m + size;
            return m;
        }

        // 2、不够分配,继续查找下一个内存池节点
        p = p->d.next;

    } while (p);

    // 重新申请一个内存块,用于小块内存分配
    return ngx_palloc_block(pool, size);
}

ngx_palloc_block申请新的内存块,尾插到内存块链表中

每次调用ngx_palloc_block函数,代表现有内存块的小块内存分配失败,此时,所有内存块的 failed + 1,表示不满足用户的需求增加 1 次。 由于采取的是尾插法,所以内存块链表中内存块的 failed 计数值依次递减。若某个内存块连续 5 次不满足用户需求,则不再使用它,下次遍历时跳过。

使用ngx_pool_t->current来记录可分配的内存块,下次遍历时,先尝试从可分配内存块的剩余空间分配。

  • 若空间足够,则返回last的地址作为内存分配的起始地址,并更新 last = last + size
  • 若空间不足,则创建一个新的结点,并更新 p->next,返回 last 的地址作为内存分配的起始地址,并更新 last = last + 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移动到可分配内存的开始位置
    m += sizeof(ngx_pool_data_t);
    // 对指针做内存对齐
    m = ngx_align_ptr(m, NGX_ALIGNMENT);
    // 设置新内存块的last
    new->d.last = m + size;

    // 将所有内存块的 failed + 1,表示不满足用户的需求 +1 次
    for (p = pool->current; p->d.next; p = p->d.next) {
		// 若某个内存块若连续 5 次都不满足用户需求,则跳过这个内存块,以后不再遍历它
        if (p->d.failed++ > 4) {
            // 调整 current 指向下一个内存块(该内存块以前的内存块无法分配内存)
            pool->current = p->d.next;
        }
    }

    // 将新创建的内存块,尾插到内存块链表
    p->d.next = new;

    return m;
}

2.2.2、大块内存分配

对于大块内存,直接申请相应大小的内存,并通过链表将已经申请的大快内存进行链接。值得注意的是,对于大块内存的管理链表节点 ngx_pool_large_t ,从内存池进行申请

在这里插入图片描述

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

        // 若连续 4 次都没找到,不找了
        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;
}

2.3、内存释放

Nginx 内存池内部只提供大块内存的释放接口,小块内存不需要释放,内存池销毁的时候随之释放。

2.3.1、大块内存释放

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

2.3.2、内存池释放

  • 查看内存池是否挂载清理函数,若有,则调用链表中的所有回调函数
  • 释放大块内存
  • 释放内存池中的内存块
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); // free
        }
    }

    // 释放内存池内存块
    for (p = pool, n = pool->d.next; ; p = n, n = n->d.next) {
        ngx_free(p); // free

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

4、参考

  • 聂松松等. Nginx底层设计与源码分析[M]. 北京:机械工业出版社,2021.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/15827.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

LeetCode刷题(python版)——Topic72. 编辑距离

一、题设 给你两个单词 word1 和 word2&#xff0c; 请返回将 word1 转换成 word2 所使用的最少操作数 。 你可以对一个单词进行如下三种操作&#xff1a; 插入一个字符删除一个字符替换一个字符 示例 1&#xff1a; 输入&#xff1a;word1 "horse", word2 &q…

回溯算法的应用

基本思想&#xff1a; 回溯法从开始结点(根结点)出发,以深度优先方式搜索整个解空间。这个开始结点成为活结点,同时也成为当前的扩展结点。在当前扩展结点处,搜索向纵深方向移至一个新结点。这个新结点成为新的活结点,开成为当前扩展结点。如果在当前扩展结点处不能再向纵深方…

大数据技术基础实验十五:Storm实验——实时WordCountTopology

大数据技术基础实验十五&#xff1a;Storm实验——实时WordCountTopology 文章目录大数据技术基础实验十五&#xff1a;Storm实验——实时WordCountTopology一、前言二、实验目的三、实验要求四、实验原理1、Topologies2、Spouts3、Bolts五、实验步骤1、导入依赖jar包2、编写代…

ffmpeg编译so

1.第一个坑&#xff1a;/bin/bash^M: bad interpreter: No such file or directory shell脚本报错/bin/bash^M: bad interpreter: No such file or directory&#xff0c;通过查阅资料得知&#xff0c;shell脚本格式必须是unix才行&#xff0c;但我这个脚本是在windows上编写完…

如何批量创建word文档并重命名?

如何批量创建word文档并重命名&#xff1f;大家请注意&#xff0c;我这里抛出的问题是批量创建word文档并重命名&#xff0c;重点在批量&#xff0c;并不是我们平时遇到的单纯创建一个或者几个word文档&#xff0c;而是批量创建几十上百个甚至几百上千个word文档。创建几个word…

英国博士后招聘|约克大学—核磁共振监测催化

英国约克大学博士后职位—核磁共振监测催化 约克大学&#xff08;University of York&#xff09;&#xff0c;建于1963年&#xff0c;是一所位于英国英格兰约克的研究型公立大学&#xff0c;英国罗素大学集团、世界大学联盟、N8大学联盟、白玫瑰大学联盟和江苏—英国高水平大学…

【开发心得】Java ftp开发注意事项

前言: 虽说已经2022年了&#xff0c;但是ftp上传方式还是有一定使用场景的&#xff0c;关于java的ftp上传下载实现&#xff0c;基本都指向了apache commont net 库。 代码实现可以参考:https://blog.csdn.net/tianshan2010/article/details/103690940 或者其他类似的文章&…

云服务--漏洞修复

1、Spring Security 身份认证绕过漏洞(CVE-2022-22978) Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架。 Spring Security存在身份认证绕过漏洞 &#xff0c;当Spring Security中使用RegexRequestMatcher进行权限配置&#…

汽车云算力“竞速”,个性化进阶成新风向

配图来自Canva可画 随着产业互联网的持续推进&#xff0c;云服务逐渐深入各行各业&#xff0c;云服务厂商也专门推出各种面向特定行业的专属云&#xff0c;比如金融云、零售云、政务云等等。如今云服务厂商正把焦点深入到汽车领域&#xff0c;围绕“汽车云”展开新的角逐。 今…

LeetCode[662]二叉树的最大宽度

难度&#xff1a;中等 题目&#xff1a; 给你一棵二叉树的根节点 root &#xff0c;返回树的 最大宽度 。 描述&#xff1a; 树的 最大宽度 是所有层中最大的 宽度 。 每一层的 宽度 被定义为该层最左和最右的非空节点&#xff08;即&#xff0c;两个端点&#xff09;之间的长…

麦子-linux驱动策略与框架

一、linux内核同步和互斥 信号量&#xff08;进程与进程&#xff09; 当进程A执行共享资源先加锁执行down表示占用资源&#xff0c;进程B想执行就会被dowm&#xff0c;导致陷入睡眠 当进程A行完了就会up释放资源&#xff0c;我们其他线程执行这个共享资源就不会被dowm睡眠 自旋…

蓝桥杯入门即劝退(十)反转链表

----------持续更新蓝桥杯入门系列算法实例------------ 如果你也喜欢Java和算法&#xff0c;欢迎订阅专栏共同学习交流&#xff01; 你的点赞、关注、评论、是我创作的动力&#xff01; -------希望我的文章对你有所帮助-------- 前言&#xff1a;如果有一定链表基础&#…

【图像处理】基于形状提取和模式匹配组合的面部特征点提取方法(Matlab代码实现)

&#x1f468;‍&#x1f393;个人主页&#xff1a;研学社的博客 &#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜…

7天酒店斩获五洲钻石奖“年度投资价值酒店连锁品牌” 打造酒店投资极致性价比

近日&#xff0c;“2022文旅国际峰会暨第十五届世界酒店论坛”在海南隆重召开&#xff0c;300多位来自中外的业界精英和跨界企业家代表等受邀参加&#xff0c;齐聚行业盛会。同期&#xff0c;“第十四届五洲钻石奖”也正式揭晓&#xff0c;7天酒店在众多品牌中脱颖而出&#xf…

【原创】使用Golang的电商搜索技术架构实现

作者&#xff1a;黑夜路人 时间&#xff1a;2022年11月 一、背景&#xff1a; 现在搜索技术已经是非常主流的应用技术&#xff0c;各种优秀的索引开源软件已经很普遍了&#xff0c;比如 Lucene/Solr/Elasticsearch 等等主流搜索索引开源软件&#xff0c;让我们搭建一个优秀的…

项目管理如何有效进行?看这篇就够了

先放上一个项目管理流程图&#xff0c;纯手打&#xff0c;图有点长。 ​ 上面的流程图其实就已经清楚概括了项目管理的流程框架&#xff0c;按照这个逻辑去推进&#xff0c;一般来说就可以达到标准化的要求了。 将以上流程细化成具体的项目管理场景&#xff0c;大致可以分为7大…

【Java面试八股文宝典之基础篇】备战2023 查缺补漏 你越早准备 越早成功!!!——Day08

大家好&#xff0c;我是陶然同学&#xff0c;软件工程大三明年实习。认识我的朋友们知道&#xff0c;我是科班出身&#xff0c;学的还行&#xff0c;但是对面试掌握不够&#xff0c;所以我将用这100多天更新Java面试题&#x1f643;&#x1f643;。 不敢苟同&#xff0c;相信大…

6种MySQL数据库平滑扩容方案剖析

1. 扩容方案剖析 1.1 扩容问题 在项目初期&#xff0c;我们部署了三个数据库A、B、C&#xff0c;此时数据库的规模可以满足我们的业务需求。为了将数据做到平均分配&#xff0c;我们在Service服务层使用uid%3进行取模分片&#xff0c;从而将数据平均分配到三个数据库中。 如…

网络丢包,网络延迟?这款神器帮你搞定所有!

常用的 ping&#xff0c;tracert&#xff0c;nslookup 一般用来判断主机的网络连通性&#xff0c;其实 Linux 下有一个更好用的网络联通性判断工具&#xff0c;它可以结合ping nslookup traceroute 来判断网络的相关特性&#xff0c;这个命令就是 mtr。 mtr 全称 my tracerout…

2022年“移动云杯”算力网络应用创新大赛圆满落幕,百万大奖揭晓!

11 月 17-18 日&#xff0c;2022 年移动云开发者技术论坛暨“移动云杯”算力网络应用创新大赛总决赛在苏州举行。活动现场公布了 2022 年“移动云杯”算力网络应用创新大赛总决赛获奖名单。同时重磅发布了移动云 openAPI 2.0、首届移动云量子计算大赛。 三大赛道齐发力&#xf…