Nginx源码阅读1-内存池

2024-09-05 14:28
文章标签 源码 内存 nginx 阅读

本文主要是介绍Nginx源码阅读1-内存池,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

首先我们来看一下他的一个基础组件:内存池组件。为什么先从内存池开始呢,因为后面 nginx 的内置数据结构,如:array,string 等都是从内存池分配的。

为什么需要内存池呢?在高并发的前提下,会大量地申请和释放小块的内存;虽然内核中也有相关的内存优化操作,但是还是容易出现大量地内存碎片,内存利用效率很低。如果我们一开始就申请一块大内存,自己对这一块大内存进行操作和管理,就可以高效的利用内存。

nginx 的内存池设计是十分巧妙的,我们一起来看一下,最好是后面手动实现一下。

数据结构

内存池结构体

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; // 日志
};

内存池数据结构体

typedef struct {u_char               *last; // 指向可用内存的起始地址u_char               *end; // 指向可用内存的末尾地址ngx_pool_t           *next; // 指向下一个内存池ngx_uint_t            failed; // 存储失败的次数
} ngx_pool_data_t;

大数据块结构体

struct ngx_pool_large_s {ngx_pool_large_t     *next; // 指向下一个大数据块void                 *alloc; // 大块数据
};

自定义清理函数结构体

struct ngx_pool_cleanup_s {ngx_pool_cleanup_pt   handler; // 清理函数void                 *data; // 存储的数据ngx_pool_cleanup_t   *next; // 下一个自定义清理函数结构
};

结构图

相关函数

创建内存池

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

销毁内存池

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

重置内存池

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

向内存池申请内存

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// 如果大小超过max,就申请大内存块return ngx_palloc_large(pool, size);
}void *
ngx_pnalloc(ngx_pool_t *pool, size_t size) // 无字节对齐
{
#if !(NGX_DEBUG_PALLOC)if (size <= pool->max) {return ngx_palloc_small(pool, size, 0);}
#endifreturn ngx_palloc_large(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;
}

开辟大内存块

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

释放大内存块

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

添加自定义清理函数

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;}// 回调函数初始化为nullc->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;
}

删除链表上的 cleanup 块

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

这个 cleanup 块可以存储任何需要清理的东西,所以他就可以操作文件,内存设备等,给这个内存池提供了很大的灵活性。

删除文件回调函数

// 可以看到上面的函数有调用这个函数的if,如果调用这个,那都会被清理
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);}
}

删除文件

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源码阅读1-内存池的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!


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

相关文章

8种快速易用的Python Matplotlib数据可视化方法汇总(附源码)

《8种快速易用的PythonMatplotlib数据可视化方法汇总(附源码)》你是否曾经面对一堆复杂的数据,却不知道如何让它们变得直观易懂?别慌,Python的Matplotlib库是你数据可视化的... 目录引言1. 折线图(Line Plot)——趋势分析2. 柱状图(Bar Chart)——对比分析3

Nginx 413修改上传文件大小限制的方法详解

《Nginx413修改上传文件大小限制的方法详解》在使用Nginx作为Web服务器时,有时会遇到客户端尝试上传大文件时返回​​413RequestEntityTooLarge​​... 目录1. 理解 ​​413 Request Entity Too Large​​ 错误2. 修改 Nginx 配置2.1

Web技术与Nginx网站环境部署教程

《Web技术与Nginx网站环境部署教程》:本文主要介绍Web技术与Nginx网站环境部署教程,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、Web基础1.域名系统DNS2.Hosts文件3.DNS4.域名注册二.网页与html1.网页概述2.HTML概述3.

Java内存区域与内存溢出异常的详细探讨

《Java内存区域与内存溢出异常的详细探讨》:本文主要介绍Java内存区域与内存溢出异常的相关资料,分析异常原因并提供解决策略,如参数调整、代码优化等,帮助开发者排查内存问题,需要的朋友可以参考下... 目录一、引言二、Java 运行时数据区域(一)程序计数器(二)Java 虚拟机栈(三)本地方法栈(四)J

NGINX 配置内网访问的实现步骤

《NGINX配置内网访问的实现步骤》本文主要介绍了NGINX配置内网访问的实现步骤,Nginx的geo模块限制域名访问权限,仅允许内网/办公室IP访问,具有一定的参考价值,感兴趣的可以了解一下... 目录需求1. geo 模块配置2. 访问控制判断3. 错误页面配置4. 一个完整的配置参考文档需求我们有一

使用Nginx配置文件服务器方式

《使用Nginx配置文件服务器方式》:本文主要介绍使用Nginx配置文件服务器方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录1. 为什么选择 Nginx 作为文件服务器?2. 环境准备3. 配置 Nginx 文件服务器4. 将文件放入服务器目录5. 启动 N

Nginx路由匹配规则及优先级详解

《Nginx路由匹配规则及优先级详解》Nginx作为一个高性能的Web服务器和反向代理服务器,广泛用于负载均衡、请求转发等场景,在配置Nginx时,路由匹配规则是非常重要的概念,本文将详细介绍Ngin... 目录引言一、 Nginx的路由匹配规则概述二、 Nginx的路由匹配规则类型2.1 精确匹配(=)2

Nginx使用Keepalived部署web集群(高可用高性能负载均衡)实战案例

《Nginx使用Keepalived部署web集群(高可用高性能负载均衡)实战案例》本文介绍Nginx+Keepalived实现Web集群高可用负载均衡的部署与测试,涵盖架构设计、环境配置、健康检查、... 目录前言一、架构设计二、环境准备三、案例部署配置 前端 Keepalived配置 前端 Nginx

java变量内存中存储的使用方式

《java变量内存中存储的使用方式》:本文主要介绍java变量内存中存储的使用方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录1、介绍2、变量的定义3、 变量的类型4、 变量的作用域5、 内存中的存储方式总结1、介绍在 Java 中,变量是用于存储程序中数据

Nginx 访问 /root/下 403 Forbidden问题解决

《Nginx访问/root/下403Forbidden问题解决》在使用Nginx作为Web服务器时,可能会遇到403Forbidden错误,文中通过示例代码介绍的非常详细,对大家的学习或者工作... 目录解决 Nginx 访问 /root/test/1.html 403 Forbidden 问题问题复现Ng