Nginx内存管理源码剖析注解

news2024/12/29 8:42:55

文章目录

    • Nginx内存池总览
    • 内存池中变量类型定义
    • 创建内存池:ngx_create_pool
    • 内存池分配空间:ngx_palloc
      • 小块内存空间分配:ngx_palloc_small
        • 创建小块内存池:ngx_palloc_block
      • 大块内存空间分配:ngx_palloc_large
      • <br />
    • 重置内存池:ngx_reset_pool
      • 释放大块内存:ngx_pfree
    • 销毁内存池:ngx_destroy_pool
      • 注册内存回收函数:ngx_pool_cleanup_add

Nginx内存池总览

在这里插入图片描述

内存池中变量类型定义

/*
 * 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.
 */
// 内存池中小内存和大内存分配的分界点:一个页的大小4k
#define NGX_MAX_ALLOC_FROM_POOL  (ngx_pagesize - 1)

// 默认的内存池大小
#define NGX_DEFAULT_POOL_SIZE    (16 * 1024)

// 内存池的对齐字节数
#define NGX_POOL_ALIGNMENT       16
// 最小内存池大小
#define NGX_MIN_POOL_SIZE                                                     \
    ngx_align((sizeof(ngx_pool_t) + 2 * sizeof(ngx_pool_large_t)),            \
              NGX_POOL_ALIGNMENT)


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


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;

// 内存池类型
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_create_pool

// 创建内存池
ngx_pool_t *
ngx_create_pool(size_t size, ngx_log_t *log)
{
    ngx_pool_t  *p; // 内存池的头部信息,记录内存池中可用内存的信息

	// 对size进行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;
}

内存池分配空间:ngx_palloc

void *
ngx_palloc(ngx_pool_t *pool, size_t size)
{
#if !(NGX_DEBUG_PALLOC)
	// 如果需要分配的内存小于等于pool->max,那么就使用小内存分配
	// 否则就使用大内存块分配。注意:pool->max最大也不能超过一个页面大小(4K)
    if (size <= pool->max) {
        return ngx_palloc_small(pool, size, 1);
    }
#endif

    return ngx_palloc_large(pool, size);
}

小块内存空间分配: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; // 从pool->current指向的大内存块进行分配内存

    do {
        m = p->d.last;

        if (align) {
			// 对可用空间的起始地址进行内存对齐
			// 这样可以减少CPU进行IO的次数,提高CPU访问数据的效率
            m = ngx_align_ptr(m, NGX_ALIGNMENT);
        }

		// 判断当前大内存块中的可用内存是否能够分配size大小的内存
		// 如果可以,就直接分配出去。否则就寻找下一个大内存块中的可用内存
        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);
}

创建小块内存池:ngx_palloc_block

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

	// 分配内存对齐之后的大内存块
	// 注意:分配的大内存块只有内存块的头信息(ngx_pool_data_t),其余的部分都没有
    m = ngx_memalign(NGX_POOL_ALIGNMENT, psize, pool->log); 
    if (m == NULL) {
        return NULL;
    }

	// new成为新大内存的头信息
    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);
    new->d.last = m + size;

	// 遍历内存块链表,并设置pool->current
    for (p = pool->current; p->d.next; p = p->d.next) {
		// 如果内存块分配内存失败的次数超过4次,那么下一次分配内存的时候
		// 就会默认该大内存块所剩的可用内存太小,不适合进行内存分配了
		// 之后分配内存的时候就会直接跳过该大内存块,使用后面的大内存块
		// 进行内存分配
        if (p->d.failed++ > 4) {
            pool->current = p->d.next;
        }
    }

	// 将新创建的大内存块串联在链表中
    p->d.next = new;

    return m;
}

大块内存空间分配:ngx_palloc_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;

	// 遍历大块内存块的链表,将其中节点中alloc变量指向NULL的节点指向刚创建的大块内存
    for (large = pool->large; large; large = large->next) {
        if (large->alloc == NULL) {
            large->alloc = p;
            return p;
        }
		// 为了减少遍历节点,所以只会从pool->large向后找三个节点
		// 如果这三个节点的alloc都不为NULL的话,就不会在找了,而是创建新的large节点
        if (n++ > 3) {
            break;
        }
    }

	// 使用小块内存分配器分配large节点的头部信息
    large = ngx_palloc_small(pool, sizeof(ngx_pool_large_t), 1);
    if (large == NULL) {
        ngx_free(p);
        return NULL;
    }

	// 将节点的alloc指针指向分配的大块内存
	// 并将头部信息节点头插到large链表中
    large->alloc = p;
    large->next = pool->large;
    pool->large = large;

    return p;
}


重置内存池:ngx_reset_pool

// 内存池中大块内存和小块内存的内存重置
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) {
		// fixbug: 除了第一个内存池,其余的内存池头部信息只有ngx_pool_data_t
		// 		   不需要跳过ngx_pool_t这么多
        p->d.last = (u_char *) p + sizeof(ngx_pool_t); 
        p->d.failed = 0; // 重置内存池分配内存失败次数
    }
	
	/*
	// 完美的重置小块内存池的方式
	for (p = pool; p; p = p->d.next) {
		if (p == pool) { // 处理第一个内存池
			p->d.last = (u_char *) p + sizeof(ngx_pool_t);
		} else { // 处理第二个及其之后的内存池
			p->d.last = (u_char *) p + sizeof(ngx_pool_data_t);
		}
		p->d.failed = 0;
	}
	*/

	// 重置内存池元信息
    pool->current = pool; // 分配内存的第一个内存池重置为pool
    pool->chain = NULL; 
    pool->large = NULL; // 重置大块内存链表
}


void *
ngx_palloc(ngx_pool_t *pool, size_t size)
{
#if !(NGX_DEBUG_PALLOC)
	// 如果需要分配的内存小于等于pool->max,那么就使用小内存分配
	// 否则就使用大内存块分配。注意:pool->max最大也不能超过一个页面大小(4K)
    if (size <= pool->max) {
        return ngx_palloc_small(pool, size, 1);
    }
#endif

    return ngx_palloc_large(pool, size);
}

释放大块内存:ngx_pfree

// 释放内存池中起始地址为p的大块内存
ngx_int_t
ngx_pfree(ngx_pool_t *pool, void *p)
{
    ngx_pool_large_t  *l;

    for (l = pool->large; l; l = l->next) {
		// 在大块内存块的链表中找到以p为起始位置的节点
		// 释放p指向大块内存块,并将其头部信息节点的alloc置为NULL
        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;
}

为什么Nginx只提供了大块内存的释放,而不提供小块内存的释放?

  1. 从小块内存的分配方式来看,小块内存无法被直接回收。因为需要被释放的小块内存的前后可能都在被使用中,所以不能直接更新last指针(可用内存空间的起始地址)或者直接释放该内存。
  2. 从Nginx的应用场景来看,Nginx是一个HTTP服务器,并且是一个基于短连接的服务器。在客户端和服务端的一次request和response之后,连接就会自动断开。即使是HTTP1.1中提供的keep-alive也会有时间限制,并不是会一直占用这个连接。当连接断开的时候,Nginx就会调用ngx_reset_pool重置整个内存池,这个时候小块内存和大块内存就都会被释放。这个内存池也可以给下一个连接进行分配内存。正是因为Nginx是一个HTTP服务器,而不是一个一直需要为一个用户提供服务的服务器,因此可以不用释放小块内存的资源。

销毁内存池:ngx_destroy_pool

// 销毁内存池
void
ngx_destroy_pool(ngx_pool_t *pool)
{
    ngx_pool_t          *p, *n;
    ngx_pool_large_t    *l;
    ngx_pool_cleanup_t  *c;

	// 执行用户注册的cleanup回调,将内存块指向的资源释放掉
    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);
        }
    }

#if (NGX_DEBUG)

    /*
     * we could allocate the pool->log from this pool
     * so we cannot use this log while free()ing the pool
     */

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

    for (p = pool, n = pool->d.next; /* void */; p = n, n = n->d.next) {
        ngx_log_debug2(NGX_LOG_DEBUG_ALLOC, pool->log, 0,
                       "free: %p, unused: %uz", p, p->d.end - p->d.last);

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

#endif

	// 释放掉所有大块内存块
    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;
        }
    }
	// 注意:由于大块内存的头信息和cleanup节点的头信息都在小块内存池中
	//       所以小块内存池一定要最后才能被销毁
}

注册内存回收函数:ngx_pool_cleanup_add

如果直接释放掉内存的话,那么内存块中指向的资源就资源泄漏了。因此用户需要将资源释放函数注册到内存池中,这样在销毁内存池的时候,就会依次将内存指向的资源,大块内存,小块内存全部释放,从而不会造成内存资源泄漏的情况。

// 注册cleanup回调函数,用于释放内存块指向的资源
// 相当于是内存块对象的析构函数了
ngx_pool_cleanup_t *
ngx_pool_cleanup_add(ngx_pool_t *p, size_t size)
{
    ngx_pool_cleanup_t  *c;

	// 创建cleanup节点
    c = ngx_palloc(p, sizeof(ngx_pool_cleanup_t));
    if (c == NULL) {
        return NULL;
    }

	// 给cleanup节点中的data开辟空间
    if (size) {
        c->data = ngx_palloc(p, size);
        if (c->data == NULL) {
            return NULL;
        }

    } else {
        c->data = NULL;
    }

    c->handler = NULL; // 清理回调函数handler置空
	
	// 将ngx_pool_cleanup_t节点头插到cleanup链表中
    c->next = p->cleanup;
    p->cleanup = c;

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

    return c;
}

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

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

相关文章

【电动车】主动配电网多源协同运行优化研究——大规模电动汽车的蒙特卡洛模拟(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

TiDB问题排查

TiDB 集群问题导图 1. 服务不可用 1.1 客户端报 "Region is Unavailable" 错误 1.1.1 "Region is Unavailable" 一般是由于 region 在一段时间不可用导致&#xff08;可能会遇到 "TiKV server is busy" 或者发送给 TiKV 的请求由于 not leader…

JMM内存模型

借鉴&#xff1a; 一文带你搞懂JMM内存模型和JVM内存区域_Apple_Web的博客-CSDN博客_jmm内存模型和jvm内存模型的区别 面试官问我什么是JMM_java技术爱好者_R的博客-CSDN博客_jmm Java内存模型 概述 Java内存模型(即Java Memory Model&#xff0c;简称JMM)本身是一种抽象的…

箭头函数带来的this变化实例

1.不使用箭头函数时 let Lesson {site: 后盾人,lists:[js,css,mysql],show: function (param) { console.log(this);// {site: 后盾人, lists: Array(3), show: ƒ}return this.lists.map(function(title){console.log(this);// Window {window: Window, self: Window, docume…

17. 老板让我手动控制网页渲染速度,说这能反爬虫?我信了。

手动数据延迟加载&#xff0c;真的可以反爬虫 爬虫训练场项目&#xff0c;加速更新中&#xff0c;专栏清单参考 pachong.vip 本次案例需要的代码量特别小&#xff0c;所以咱们再 Nginx 中也进行一下相关配置 文章目录页面逻辑实现接口逻辑实现延迟实现&#xff0c;time.sleep()…

2022年需求最大8种编程语言!(详细解读)

DevJobsScanner 在过去的 14 个月&#xff08;从 2021 年 10 月到 2022 年 11 月&#xff09;中分析了超过 1200 万个开发人员职位需求&#xff0c;并从其中挑选了明确需要编程语言的工作机会&#xff0c;得到了 2022 年最受欢迎的 8 种编程语言。 目前市场中需求最高的前八位…

数学建模学习笔记-算法(线性规划模型)-上

目录 线性规划问题 线性规划的matlab标准形式 解析 目标函数 约束条件 使用matlab的linprog函数来进行求解 线性规划问题 数学规划&#xff1a;安排现有资源安排生产&#xff0c;以取得最大效益的问题。 线性规划&#xff1a;目标函数和约束条件均为线性函数 在一组线性…

2023/1/2总结

今天AC了三个有关二叉树的题目&#xff1a; P1827 [USACO3.4] 美国血统 American Heritage_lxh0113的博客-CSDN博客 https://blog.csdn.net/lxh0113/article/details/128522831?spm1001.2014.3001.5502 P1030 [NOIP2001 普及组] 求先序排列_lxh0113的博客-CSDN博客 然后学…

web基础标签

标签分类&#xff1a; 文本标签&#xff1a; 文本标题标签&#xff1a;h1---h6 段落标签&#xff1a; p 水平线&#xff1a; <hr/> 换行符&#xff1a; <br/> 转义字符&#xff1a; 注释标签&#xff1a; <!--注释内容--> 无语义标签&#xff1a; 语义标签…

educoder数据结构与算法 线性表 第1关:实现一个顺序存储的线性表

本文已收录于专栏 &#x1f332;《educoder数据结构与算法_大耳朵宋宋的博客-CSDN博客》&#x1f332; &#x1f350;任务描述&#x1f350; 本关任务&#xff1a;实现 step1/Seqlist.cpp 中的SL_InsAt、SL_DelAt和SL_DelValue三个操作函数&#xff0c;以实现线性表中数据的插…

TypeScript中abstract抽象类、抽象成员

TypeScript也支持定义抽象类和抽象类成员。抽象类和抽象类成员都使用abstract关键字来定义 抽象类可以不包含抽象方法&#xff0c;但抽象方法必须存在于抽象类中抽象方法只能定义&#xff0c;不能实现&#xff0c;即没有函数体抽象类不能被直接使用&#xff0c;只能被继承&…

Spring Boot学习篇(五)

Spring Boot学习篇(五) mybatis-plus使用 1.1 配置pom.xml文件 <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:s…

《设计模式》代理模式

《设计模式》设计模式的基本原则 《设计模式》单例模式 《设计模式》工厂模式 《设计模式》原型模式 《设计模式》建造者模式 《设计模式》适配器模式 《设计模式》桥接模式 《设计模式》装饰者模式 《设计模式》组合模式 《设计模式》外观模式 《设计模式》享元模式 《设计模式…

HTML5和CSS3 WEB技术开发

HTML5和CSS3 WEB技术开发 B站视频参考&#xff1a;https://www.bilibili.com/video/BV1H44y1k7ze/ 课程目标&#xff1a; 使用HTML5进行网站布局使用CSS3进行网站美化开发精美的商业网站 第一章 HTML5基础 概念&#xff1a; ​ 网页 &#xff1a;互联网的基础&#xff0c;网…

requests请求库(爬取)

文章目录requests模块链接拼接&#xff08;params参数&#xff09;UA伪装&#xff08;headers参数&#xff09;POST请求页面局部信息爬取&#xff08;GET&#xff09;爬取国家药品监督管理监督总局中基于中华人民共和国化妆品生产许可证相关数据爬取图片爬虫分类通用爬虫&#…

分布式存储从FastDFS切换到Minio

什么是Minio 基于官网的介绍如下&#xff1a;MinIO 是一款高性能、分布式的对象存储系统. 它是一款软件产品, 可以100%的运行在标准硬件。即X86等低成本机器也能够很好的运行MinIO。 从官网的介绍可以看出Minio是一款和FastDFS类似的工具&#xff0c;分布式存储系统。目前在使…

运行MAT项目环境配置中出现的问题及参考方案

MAT项目是用于修复图片中缺失的部分&#xff1a;及为图像中缺失的区域产生视觉吸引力和语义适当的内容。 项目链接&#xff1a;GitHub - fenglinglwb/MAT: MAT: Mask-Aware Transformer for Large Hole Image InpaintingMAT: Mask-Aware Transformer for Large Hole Image Inp…

国内有没有可以全职远程办公的程序员工作?

明作为一个曾经靠兼职开发远程办公来赚钱的程序员&#xff0c;既碰到过无良甲方&#xff0c;开发完了不结尾款&#xff0c;最后通过法律手段才解决问题&#xff1b;也接过自称甲方的中介单&#xff0c;耗费心力拿到尾款&#xff0c;最后发现人家拿的钱比自己还多......这一路兼…

方格取数--数字三角形dp问题

项目场景&#xff1a; 线性dp 数字三角形类问题 问题描述 设有 NN 的方格图&#xff0c;我们在其中的某些方格中填入正整数&#xff0c;而其它的方格中则放入数字0。如下图所示&#xff1a; 某人从图中的左上角 A 出发&#xff0c;可以向下行走&#xff0c;也可以向右行走&am…

WebGL及Threejs学习介绍

一、学习背景及实现的效果 这十年来Web得到了飞速的发展&#xff0c;随着WebGL的普及&#xff0c;网页的表现能力越来越强大&#xff0c;网页上已经可以开始做出很多复杂的动画、精美的效果&#xff1b;还能通过WebGL在网页中绘制高性能的3d图形。随着浏览器的性能和网络、带宽…