nginx内存池基本原理及问题
来源:互联网 发布:python 替换字符串 编辑:程序博客网 时间:2024/05/17 22:10
nginx的内存池相关文章已经很多了,这里写一下简单原理和最近碰到的问题。
用到的几个结构,相应说明请看注释:
//每次能从pool分配的最大内存块大小,ngx_pagesize在X86下一般是4096,即4k,也就是说每次能从pool分配的最大内存块大小为4095字节,将近4k#define NGX_MAX_ALLOC_FROM_POOL (ngx_pagesize - 1)//默认pool大小:16k#define NGX_DEFAULT_POOL_SIZE (16 * 1024)struct ngx_pool_large_s { /*大块内存数据结构*/ ngx_pool_large_t *next; /*其实是一个头插法的单链表,每次分配一个大块内存都将列表节点插入到这个链表的表头*/ void *alloc; /*大块内存是直接用malloc来分配的,alloc就是用来保存分配到的内存地址*/};typedef struct { /*一个内存池是由多个pool节点组成的链,这个结构用来链接各个pool节点和保存pool节点可用的内存区域起止地址*/ u_char *last; /*当前内存分配结束位置,即下一段可分配内存的起始位置*/ u_char *end; /*该pool节点内存结束地址*/ ngx_pool_t *next; /*下一个pool节点地址*/ ngx_uint_t failed;/*该pool节点分配内存失败的次数*/} ngx_pool_data_t;struct ngx_pool_s { /*内存池控制结构*/ ngx_pool_data_t d; /*当前pool节点信息*/ size_t max; /*一次能分配的最大内存大小*/ ngx_pool_t *current; /*用来保存当前从哪个pool上分配内存的pool指针,每次分配内存都会从current指向的pool上分配*/ ngx_chain_t *chain; ngx_pool_large_t *large; /*大块内存列表,*/ ngx_pool_cleanup_t *cleanup; ngx_log_t *log;};
先看一下创建内存池的实现代码,不到20行的代码,很简单的:
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;}
从上面的代码可以知道max最大为4095,也就是说每次申请的内存最大大小为4095字节,超出则使用大块内存(参考ngx_palloc和ngx_palloc_large的实现,这里不讲了)。在X64下(下同),调用pool = ngx_create_pool(NGX_DEFAULT_POOL_SIZE, log)后,得到的pool内存大小及布局如下:
last和end指针指向的内存区域就是可用空间,pool头已经占80个字节了,所以可用空间比创建时指定的pool大小少80个字节,这里是我觉得设计得不合理的地方,这个pool的大小应该等于用户在创建pool时指定的大小加上pool头大小,这样用户创建了多少就能用多少。
得到pool之后就可以从里面分配内存了,先来看一下分配内存的函数实现,也是几行代码:
void *ngx_palloc(ngx_pool_t *pool, size_t size){ u_char *m; ngx_pool_t *p; if (size <= pool->max) { p = pool->current; do { m = ngx_align_ptr(p->d.last, 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); } return ngx_palloc_large(pool, size);}
比如调用p = ngx_palloc(pool, 32); last会向后移动32个字节,内存布局如下:
假设这个pool可用空间不够了,那么会调用ngx_palloc_block分配一个与当前pool大小一样的pool,并将该pool挂在内存池链上和将last指针调整好之后直接返回给用户可用的内存地址,如下图是调用q = ngx_palloc(pool, 128);后的内存布局,左边是可用空间不够的pool,右边是ngx_palloc_block分配的pool:
以上就是nginx内存池的基本原理,首先分配一个大块内存,然后每次分配小块内存时直接修改last指针后直接返回内存地址,非常之高效。
基本原理讲完了,再来看看ngx_palloc的bug,这个bug隐藏得比较深,耗费了好几天才搞定。这个bug是我同事KawaruNagisa发现的,他也给官网提bug并accept了,相信下个版本会得到修复的。我接触nginx时间不是很长,这个bug正好有机会来研究研究nginx的内存池实现。先来看看ngx_palloc的几行代码:
void *ngx_palloc(ngx_pool_t *pool, size_t size){…… m = ngx_align_ptr(p->d.last, NGX_ALIGNMENT); //内存对齐,在64位系统下是8字节对齐 if ((size_t) (p->d.end - m) >= size) { p->d.last = m + size; return m; }……}
上面的代码中本意是先得到对齐之后的m,并和end指针对比,如果end和m之间的可用空间大于等于要分配的size,那么就分配成功,并将last向后移动。但是还有另一种情况没有考虑到:假设 last=28, end=30, size = 32, end-last=2,即end和last之间只有2个字节的可用空间,那么将last 8字节对齐之后为32,即m=32,那么end-m=-2,-2再转换成size_t则变成了18446744073709551614,再跟size相比,肯定为true,接着调整last指针并返回m;而我们要分配32个字节,应该再分配一个pool并在这个pool上分配内存,显然是bug啊。用gdb模拟一下:
所以上面代码中if条件里应该加上p->d.end > m ,修复后的ngx_palloc应该如下:
void *ngx_palloc(ngx_pool_t *pool, size_t size){ u_char *m; ngx_pool_t *p; if (size <= pool->max) { p = pool->current; do { m = ngx_align_ptr(p->d.last, NGX_ALIGNMENT); if (p->d.end > m && (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); } return ngx_palloc_large(pool, size);}
另一种避免这个Bug的方法是创建pool时(ngx_create_pool)指定的size要按NGX_ALIGNMENT字节对齐,否则比较容易出Bug。
http://blog.csdn.net/v_july_v/article/details/7040425
- nginx内存池基本原理及问题
- nginx内存池ngx_pool_t 及内存管理
- nginx的内存池及内存管理
- nginx内存池c++代码及测试
- Nginx源码分析---内存池结构ngx_pool_t及内存管理
- Nginx内存池结构ngx_pool_t及内存管理
- KVM基本原理及架构四-内存虚拟化
- 内存管理基本原理及非ARC环境使用小心得
- Nginx的基本原理
- nginx内存池管理
- nginx中的内存池
- [zz] nginx 内存池
- nginx内存池管理
- nginx中的内存池
- nginx中的内存池
- nginx内存池管理
- nginx 内存池
- Nginx内存池
- 第17周项目7-电子词典结构体版
- Qt QLineEdit中屏蔽粘贴功能
- Device Tree(二):基本概念
- 谈Ajax的Get和Post的区别
- Oracle 数据库实例、监听器、EM
- nginx内存池基本原理及问题
- Device Tree(三):代码分析
- 第十七周项目二引用作形参
- vs制作库
- 杂七杂八
- thinkPHP 模板的使用技巧(十三)
- 对SET UNUSED Clause本质的理解(以及 SET UNUSED Clause和 DROP(column) clause的关系 )
- IOS 技术箴言(自留)
- SaaS