6.nginx源码分析之数据结构:ngx_pool_t
来源:互联网 发布:闽江网络教学平台 编辑:程序博客网 时间:2024/06/06 14:04
nginx内存管理
nginx内存管理的结构体
关于nginx内存管理的方法在ngx_palloc.h文件中,定义了相关结构体和接口:
如下几个宏定义了内存池的默认大小和对齐大小。
/* * 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. */#define NGX_MAX_ALLOC_FROM_POOL (ngx_pagesize - 1)//默认大小是1024个16B#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)
定义了nginx的FREE函数指针,ngx_pool_cleanup_pt为一个函数指针,他可以指向用户希望采用的释放函数。
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;};
handler 记录着资源的释放策略;
data是实际所指向的资源的地址;
next就是链表的下一个节点位置;
因为nginx的内存释放是在最后进行的,所以需要记录下每个资源的释放规则(由handler记录着函数地址)。
typedef struct ngx_pool_large_s ngx_pool_large_t;struct ngx_pool_large_s { ngx_pool_large_t *next; //指向下一个pool void *alloc; //指向分配的内存};
typedef struct { u_char *last; //内存池下一次分配的起始位置 u_char *end; //内存池结束位置 ngx_pool_t *next; //内存池里面有很多块内存,这些内存块就是通过该指针连成链表的 ngx_uint_t failed; //内存分配失败次数} ngx_pool_data_t; //内存池数据块信息
内存池的控制信息如下,上述内存池数据块ngx_pool_data_t信息是当前这个内存池的描述信息,所以其实是ngx_pool_s的一部分,作为控制信息我们把它放在了ngx_pool_s的开始位置。
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;};
d 是当前内存块的控制信息;
max 是小块内存申请的最大值,如果比这个大就算是大块内存;
current 当前内存池本身的地址
chain 指向了一个chain结构
large 指向一个大块内存的地址(大小大鱼max的内存,nginx使用标准库malloc进行申请)
cleanup 指向nginx释放结构体的指针
log 指向日志结构体
各个结构体的关系如下图所示(该图由JULY绘制,借鉴过来):
nginx内存管理的接口
上述是内存管理的结构体定义,在C编程中,一旦结构体定义OK,剩下的接口实现基本都是围绕着结构体展开的。nginx向我们介绍了如下几个接口定义:
//nginx的内存申请void *ngx_alloc(size_t size, ngx_log_t *log);void *ngx_calloc(size_t size, ngx_log_t *log);//nginx创建内存池,销毁内存池,重置内存池ngx_pool_t *ngx_create_pool(size_t size, ngx_log_t *log);void ngx_destroy_pool(ngx_pool_t *pool);void ngx_reset_pool(ngx_pool_t *pool);//在内存池进行资源的申请和释放void *ngx_palloc(ngx_pool_t *pool, size_t size);void *ngx_pnalloc(ngx_pool_t *pool, size_t size);void *ngx_pcalloc(ngx_pool_t *pool, size_t size);void *ngx_pmemalign(ngx_pool_t *pool, size_t size, size_t alignment);ngx_int_t ngx_pfree(ngx_pool_t *pool, void *p);ngx_pool_cleanup_t *ngx_pool_cleanup_add(ngx_pool_t *p, size_t size);void ngx_pool_run_cleanup_file(ngx_pool_t *p, ngx_fd_t fd);void ngx_pool_cleanup_file(void *data);void ngx_pool_delete_file(void *data);
下面我们对这些接口进行逐个分析,接口的实现在ngx_palloc.c文件中:
ngx_alloc
void * ngx_alloc(size_t size, ngx_log_t *log){ void *p; p = malloc(size); //ngx_alloc申请内存只是对malloc的封装 //不管是申请成功还是失败都会写入日志 if (p == NULL) { ngx_log_error(NGX_LOG_EMERG, log, ngx_errno, "malloc(%uz) failed", size); } ngx_log_debug2(NGX_LOG_DEBUG_ALLOC, log, 0, "malloc: %p:%uz", p, size); return p;}
ngx_calloc
void * ngx_calloc(size_t size, ngx_log_t *log){ void *p; p = ngx_alloc(size, log); if (p) { ngx_memzero(p, size); //将ngx_alloc申请的内存全部清空 } return p;}
ngx_create_pool 创建ngx内存池操作
ngx_pool_t * ngx_create_pool(size_t size, ngx_log_t *log){ ngx_pool_t *p; //创建nginx内存池的控制结构 p = ngx_memalign(NGX_POOL_ALIGNMENT, size, log); if (p == NULL) { return NULL; } //对nginx控制结构的值进行初始化 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); //可申请的内存大小要去除控制信息(ngx_pool_t) p->max = (size < NGX_MAX_ALLOC_FROM_POOL) ? size : NGX_MAX_ALLOC_FROM_POOL; //判定大内存的大小 p->current = p; //当前内存块的地址指向p本身 p->chain = NULL; //链表指针地址 p->large = NULL; //刚开始还没有大内存申请,所以指针为NULL p->cleanup = NULL; //清空模块的指针为空 p->log = log; //日志结构体指针进行初始化 return p; }
内存池的初始化没有采用malloc直接申请,而是使用了ngx_memalign函数,ngx_memalign的实现如下:
void * ngx_memalign(size_t alignment, size_t size, ngx_log_t *log){ void *p; int err; err = posix_memalign(&p, alignment, size); if (err) { ngx_log_error(NGX_LOG_EMERG, log, err, "posix_memalign(%uz, %uz) failed", alignment, size); p = NULL; } ngx_log_debug3(NGX_LOG_DEBUG_ALLOC, log, 0, "posix_memalign: %p:%uz @%uz", p, size, alignment); return p;}
这个函数中真正起申请作用的是posix_memalign,posix_memalign的接口定义如下:
int posix_memalign (void **memptr, //内存申请指针的地址 size_t alignment, //申请的内存是alignment的倍数,alignment的2的幂,这样设计是为了方便操作 size_t size); //size是我们申请的内存的大小
ngx_create_pool 调用最终会得到一个内存池的控制信息以及对其成员的初始化:
ngx_destroy_pool内存池的销毁操作
代码实现如下:
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); } }#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; } }}
在C语言中,因为没有类的概念,也就不存在类的销毁,所以资源的释放顺序一定要严格的把控。以ngx_pool_t为例,它其中包含了ngx_pool_large_t和ngx_pool_cleanup_t这些结构体指针,释放顺序如下:
(1)首先在清楚链表中使用用户指定的方式( handler
所指向的函数)释放节点的data数据。
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); } }
(2)接着释放大块内存链表,其中的ngx_free就是标准库中的free函数封装:
for (l = pool->large; l; l = l->next) { if (l->alloc) { ngx_free(l->alloc); } }
(3)最后我们释放nginx的内存池链表,这里采用的是双指针联动方式,让p指向当前内存池地址,而n指向下一个内存池,先释放当前的内存池,再判断下一个内存池是否存在,如果不存在则循环结束:
for (p = pool, n = pool->d.next; /* void */; p = n, n = n->d.next) { ngx_free(p); if (n == NULL) { break; } }
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) { p->d.last = (u_char *) p + sizeof(ngx_pool_t); p->d.failed = 0; } pool->current = pool; pool->chain = NULL; pool->large = NULL;}
这个流程和销毁内存池操作有着相同的部分,先清空大内存池的链表,之前的那个cleanup链表的销毁操作可以不进行,反正在内存池执行销毁的时候也会执行,然后把last指针指向控制信息的下一个字节地址。
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);}
这里主要关注两个函数ngx_palloc_small和ngx_palloc_large。
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); } 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);}
在整个内存池链表中找到能够申请size大小的内存,把申请的起始地址返回,该内存池的可用内存大小减少size。如果遍历完当前的内存池链表都无法满足当前size大小的内存申请。则调用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;}
该函数的作用就是申请了一个新的内存池(pool),首先计算了内存池的大小,然后进行申请,如果成功的话把地址返回给new指针,用new对内存池的结构体成员进行初始化,m是我们最后申请的内存块的首地址,但是现在需要把new这个新的内存池插入到其他的内存池的链表末尾。
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; //大块内存的申请采用malloc直接申请 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; } //把这个p所指向的大块内存的管理节点尾部添加到大块内存链表指针里 large->alloc = p; large->next = pool->large; pool->large = large; return p;}
ngx_pnalloc不进行对其的内存池申请
该方法的实现和ngx_palloc类似
ngx_pcalloc对内存池申请的内存初始化为0
void * ngx_pcalloc(ngx_pool_t *pool, size_t size){ void *p; p = ngx_palloc(pool, size); if (p) { //初始化申请的内存为0 ngx_memzero(p, size); } return p;}
ngx_pfree 内存池释放大块内存的方法
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;}
ngx_pool_run_cleanup_file清空文件描述符fd
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; } } }}
小结
关于nginx的内存池管理如上所示,需要边看代码边进行画图才能加深理解,管理思路比较直接,不像c++的stl那样分配了多个自由链表。希望大家可以好好思考。
- 6.nginx源码分析之数据结构:ngx_pool_t
- Nginx基本数据结构之ngx_pool_t
- nginx数据结构(ngx_pool_t)
- Nginx源码分析(3)之——内存池(ngx_pool_t)分析
- Nginx源码分析---内存池结构ngx_pool_t及内存管理
- Nginx源码分析——ngx_pool_t内存池
- Nginx源码阅读(ngx_pool_t)
- Nginx源码分析之基本数据结构
- 5.nginx源码分析之数据结构:ngx_string
- 7.nginx源码分析之数据结构:ngx_array_t
- 8.nginx源码分析之数据结构:ngx_list_t
- 9.nginx源码分析之数据结构:ngx__queue_t
- 10.nginx源码分析之数据结构:ngx__rbtree_t
- 菜鸟nginx源码剖析数据结构篇(九) 内存池ngx_pool_t
- 菜鸟nginx源码剖析数据结构篇(九) 内存池ngx_pool_t
- 菜鸟nginx源码剖析数据结构篇(九) 内存池ngx_pool_t
- 菜鸟nginx源码剖析数据结构篇(九) 内存池ngx_pool_t
- nginx源码分析—内存池结构ngx_pool_t及内存管理
- POJ Problem 3940 Grey Area
- 根据ip地址获取所在省市城市地区的真实地址/php函数
- eBay校园招聘编程题-淘气的小明
- 第9周项目3-稀疏矩阵的三元组表示的实现及应用(1)
- 1042 字符统计
- 6.nginx源码分析之数据结构:ngx_pool_t
- js地址栏加密传参
- Android偏好设置SharedPreference你不知道的秘密
- 数据结构实验之二叉树一:树的同构数据结构实验之二叉树一:树的同构 Time Limit: 1000MS Memory Limit: 65536KB Submit Statistic Problem D
- 第九周项目2-对称矩阵压缩存储的实现与应用(1)
- 【第九周项目3-稀疏矩阵的三元组表示的实现及应用(2)】
- 6个在线正则表达式工具
- 第八周项目5 计数的模式匹配
- 【第九周 项目3-稀疏矩阵的三元组表示的实现及应用(1)】