nginx内存池详解Word文档格式.docx
《nginx内存池详解Word文档格式.docx》由会员分享,可在线阅读,更多相关《nginx内存池详解Word文档格式.docx(22页珍藏版)》请在冰点文库上搜索。
∙如free/malloc/memalign/posix_memalign,分别被封装为ngx_free,ngx_alloc/ngx_calloc,ngx_memalign
∙ngx_alloc:
封装malloc分配内存
∙ngx_calloc:
封装malloc分配内存,并初始化空间内容为0
∙ngx_memalign:
返回基于一个指定alignment的大小为size的内存空间,且其地址为alignment的整数倍,alignment为2的幂。
(2)./src/core/ngx_palloc.h/.c
∙封装创建/销毁内存池,从内存池分配空间等函数
.表示nginx-1.0.4代码目录,本文为/usr/src/nginx-1.0.4。
nginx对内存的管理均统一完成,例如,在特定的生命周期统一建立内存池(如main函数系统启动初期即分配1024B大小的内存池),需要内存时统一分配内存池中的内存,在适当的时候释放内存池的内存(如关闭http链接时调用ngx_destroy_pool进行销毁)。
因此,开发者只需在需要内存时进行申请即可,不用过多考虑内存的释放等问题,大大提高了开发的效率。
先看一下内存池结构。
此处统一一下概念,内存池的数据块:
即分配内存在这些数据块中进行,一个内存池可以有多一个内存池数据块。
nginx的内存池结构如下。
00048:
typedefstruct{
00049:
u_char*last;
//当前内存池分配到此处,即下一次分配从此处开始
00050:
u_char*end;
//内存池结束位置
00051:
ngx_pool_t*next;
//内存池里面有很多块内存,这些内存块就是通过该指针连成链表的
00052:
ngx_uint_tfailed;
//内存池分配失败次数
00053:
}ngx_pool_data_t;
//内存池的数据块位置信息
00054:
00055:
00056:
structngx_pool_s{//内存池头部结构
00057:
ngx_pool_data_td;
//内存池的数据块
00058:
size_tmax;
//内存池数据块的最大值
00059:
ngx_pool_t*current;
//指向当前内存池
00060:
ngx_chain_t*chain;
//该指针挂接一个ngx_chain_t结构
00061:
ngx_pool_large_t*large;
//大块内存链表,即分配空间超过max的内存
00062:
ngx_pool_cleanup_t*cleanup;
//释放内存池的callback
00063:
ngx_log_t*log;
//日志信息
00064:
};
其中,sizeof(ngx_pool_data_t)=16B,sizeof(ngx_pool_t)=40B。
nginx将几乎所有的结构体放在ngx_core.h文件中重新进行了申明,如下。
typedefstructngx_module_sngx_module_t;
typedefstructngx_conf_sngx_conf_t;
typedefstructngx_cycle_sngx_cycle_t;
typedefstructngx_pool_sngx_pool_t;
typedefstructngx_chain_sngx_chain_t;
typedefstructngx_log_sngx_log_t;
typedefstructngx_array_sngx_array_t;
typedefstructngx_open_file_sngx_open_file_t;
typedefstructngx_command_sngx_command_t;
typedefstructngx_file_sngx_file_t;
typedefstructngx_event_sngx_event_t;
typedefstructngx_event_aio_sngx_event_aio_t;
typedefstructngx_connection_sngx_connection_t;
其他与内存池相干的数据结构,如清除资源的cleanup链表,分配的大块内存链表等,如下。
00015:
/*
00016:
*NGX_MAX_ALLOC_FROM_POOLshouldbe(ngx_pagesize-1),i.e.4095onx86.
00017:
*OnWindowsNTitdecreasesanumberoflockedpagesinakernel.
00018:
*/
00019:
#defineNGX_MAX_ALLOC_FROM_POOL(ngx_pagesize-1)//在x86体系结构下,该值一般为4096B,即4K
00020:
00021:
#defineNGX_DEFAULT_POOL_SIZE(16*1024)
00022:
00023:
#defineNGX_POOL_ALIGNMENT16
00024:
#defineNGX_MIN_POOL_SIZE\
00025:
ngx_align((sizeof(ngx_pool_t)+2*sizeof(ngx_pool_large_t)),\
00026:
NGX_POOL_ALIGNMENT)
00027:
00028:
00029:
typedefvoid(*ngx_pool_cleanup_pt)(void*data);
//cleanup的callback类型
00030:
00031:
typedefstructngx_pool_cleanup_sngx_pool_cleanup_t;
00032:
00033:
structngx_pool_cleanup_s{
00034:
ngx_pool_cleanup_pthandler;
00035:
void*data;
//指向要清除的数据
00036:
ngx_pool_cleanup_t*next;
//下一个cleanupcallback
00037:
00038:
00039:
00040:
typedefstructngx_pool_large_sngx_pool_large_t;
00041:
00042:
structngx_pool_large_s{
00043:
ngx_pool_large_t*next;
//指向下一块大块内存
00044:
void*alloc;
//指向分配的大块内存
00045:
...
00067:
00068:
ngx_fd_tfd;
00069:
u_char*name;
00070:
00071:
}ngx_pool_cleanup_file_t;
00072:
(gdb)pgetpagesize()
$18=4096
全局变量ngx_pagesize的初始化是在如下函数中完成的。
./src/os/unix/ngx_posix_init.c
ngx_int_t
ngx_os_init(ngx_log_t*log)
{
ngx_uint_tn;
#if(NGX_HAVE_OS_SPECIFIC_INIT)
if(ngx_os_specific_init(log)!
=NGX_OK){
returnNGX_ERROR;
}
#endif
ngx_init_setproctitle(log);
/**该函数为glibc的库函数,由系统调用实现,返回内核中的PAGE_SIZE,该值依赖体系结构*/
ngx_pagesize=getpagesize();
ngx_cacheline_size=NGX_CPU_CACHE_LINE;
...
}
这些数据结构之间的关系,请参考后面的图。
这些数据结构逻辑结构图如下。
注:
本文采用UML的方式画出该图。
创建内存池有ngx_create_pool()函数完成,代码如下。
ngx_pool_t*
ngx_create_pool(size_tsize,ngx_log_t*log)
{
ngx_pool_t*p;
p=ngx_memalign(NGX_POOL_ALIGNMENT,size,log);
if(p==NULL){
returnNULL;
p->
d.last=(u_char*)p+sizeof(ngx_pool_t);
//last指向ngx_pool_t结构体之后数据取起始位置
d.end=(u_char*)p+size;
//end指向分配的整个size大小的内存的末尾
d.next=NULL;
d.failed=0;
size=size-sizeof(ngx_pool_t);
max=(size<
NGX_MAX_ALLOC_FROM_POOL)?
size:
NGX_MAX_ALLOC_FROM_POOL;
//最大不超过4095B
current=p;
chain=NULL;
large=NULL;
cleanup=NULL;
log=log;
returnp;
例如,调用ngx_create_pool(1024,0x80d1c4c)后,创建的内存池物理结构如下图。
销毁内存池由如下函数完成。
voidngx_destroy_pool(ngx_pool_t*pool)
该函数将遍历内存池链表,所有释放内存,如果注册了clenup(也是一个链表结构),亦将遍历该cleanup链表结构依次调用clenup的handler清理。
同时,还将遍历large链表,释放大块内存。
重置内存池由下面的函数完成。
voidngx_reset_pool(ngx_pool_t*pool);
该函数将释放所有large内存,并且将d->
last指针重新指向ngx_pool_t结构之后数据区的开始位置,同刚创建后的位置相同。
内存分配的函数如下。
void*ngx_palloc(ngx_pool_t*pool,size_tsize);
void*ngx_pnalloc(ngx_pool_t*pool,size_tsize);
void*ngx_pcalloc(ngx_pool_t*pool,size_tsize);
void*ngx_pmemalign(ngx_pool_t*pool,size_tsize,size_talignment);
返回值为分配的内存起始地址。
选择其中的两个函数进行分析,其他的也很好理解,省略。
ngx_palloc()代码如下,分析请参考笔者所加的注释。
00115:
void*
00116:
ngx_palloc(ngx_pool_t*pool,size_tsize)
00117:
00118:
u_char*m;
00119:
00120:
00121:
if(size<
=pool->
max){//判断待分配内存与max值
00122:
00123:
p=pool->
current;
//小于max值,则从current节点开始遍历pool链表
00124:
00125:
do{
00126:
m=ngx_align_ptr(p->
d.last,NGX_ALIGNMENT);
//指针对齐
00127:
00128:
if((size_t)(p->
d.end-m)>
=size){
00129:
d.last=m+size;
//在该节点指向的内存块中分配size大小的内存
00130:
00131:
returnm;
00132:
00133:
00134:
p=p->
d.next;
//遍历到下一个数据块
00135:
00136:
}while(p);
00137:
00138:
returnngx_palloc_block(pool,size);
//链表里没有能分配size大小内存的节点,则生成一个新的节点并在其中分配内存
00139:
00140:
00141:
returnngx_palloc_large(pool,size);
//大于max值,则在large链表里分配内存
00142:
关于ngx_align_ptr宏:
#definengx_align_ptr(p,a)\
(u_char*)(((uintptr_t)(p)+((uintptr_t)a-1))&
~((uintptr_t)a-1))
例如,在2.1节中创建的内存池中分配200B的内存,调用ngx_palloc(pool,200)后,该内存池物理结构如下图。
ngx_palloc_block函数代码如下,分析请参考笔者所加的注释。
00175:
staticvoid*
00176:
ngx_palloc_block(ngx_pool_t*pool,size_tsize)
00177:
00178:
00179:
size_tpsize;
00180:
ngx_pool_t*p,*new,*current;
00181:
00182:
psize=(size_t)(pool->
d.end-(u_char*)pool);
//计算pool的大小
00183:
00184:
m=ngx_memalign(NGX_POOL_ALIGNMENT,psize,pool->
log);
//分配一块与pool大小相同的内存
00185:
if(m==NULL){
00186:
00187:
00188:
00189:
new=(ngx_pool_t*)m;
00190:
00191:
new->
d.end=m+psize;
//设置end指针
00192:
00193:
00194:
00195:
m+=sizeof(ngx_pool_data_t);
//让m指向该块内存ngx_pool_data_t结构体之后数据区起始位置
00196:
m=ngx_align_ptr(m,NGX_ALIGNMENT);
//按4字节对齐
00197:
//在数据区分配size大小的内存并设置last指针
00198:
00199:
current=pool->
00200:
00201:
for(p=current;
d.next){
00202:
if(p->
d.failed++>
4){//failed的值只在此处被修改
00203:
current=p->
//失败4次以上移动current指针(失败次数大于5时,不再使用该内存块)
00204:
00205:
00206:
00207:
d.next=new;
//将这次分配的内存块new加入该内存池
00208:
00209:
pool->
current=current?
current:
new;
00210:
00211:
00212:
注意:
该函数分配一块内存后,last指针指向的是ngx_pool_data_t结构体(大小16B)之后数据区的起始位置。
而创建内存池时时,last指针指向的是ngx_pool_t结构体(大小40B)之后数据区的起始位置。
结合2.7节的内存池的物理结构,更容易理解。
请参考如下函数,不再赘述。
ngx_int_t
ngx_pfree(ngx_pool_t*pool,void*p)
需要注意的是该函数只释放large链表中注册的内存,普通内存在ngx_destroy_pool中统一释放。
请参考如下函数,该函数实现也很简单,此处不再赘述。
ngx_pool_cleanup_t*ngx_pool_cleanup_add(ngx_pool_t*p,size_tsize)
针对本文第3节的例子,画出的内存池的物理结构如下图。
从该图也能看出2.4节的结论,即内存池第一块内存前40字节为ngx_pool_t结构,后续加入的内存块前16个字节为ngx_pool_data_t结构,这两个结构之后便是真正可以分配内存区域。
因此,本文Reference中的内存分配相关中的图是有一点点小问题的,并不是每一个节点的前面都是ngx_pool_t结构。
理解并掌握开源软件的最好方式莫过于自己写一些测试代码,或者改写软件本身,并进行调试来进一步理解开源软件的原理和设计方法。
本节给出一个创建内存池并从中分配内存的简单例子。
/**
*ngx_pool_ttest,totestngx_palloc,ngx_palloc_block,ngx_palloc_large
#include<
stdio.h>
#include"
ngx_config.h"
ngx_conf_file.h"
nginx.h"
ngx_core.h"
ngx_string.h"
ngx_palloc.h"
volatilengx_cycle_t*ngx_cycle;
voidngx_log_error_core(ngx_uint_tlevel,ngx_log_t*log,ngx_err_terr,
constchar*fmt,...)
voiddump_pool(ngx_pool_t*pool)
while(pool)
printf("
pool=0x%x\n"
pool);
.d\n"
);
.last=0x%x\n"
pool->
d.last);
.end=0x%x\n"
d.end);
.next=0x%x\n"
d.next);
.failed=%d\n"
d.failed);
.max=%d\n"
max);
.current=0x%x\n"
current);
.chain=0x%x\n"
chain);
.large=0x%x\n"
large);
.cleanup=0x%x\n"
cleanup);
.log=0x%x\n"
availablepoolmemory=%d\n\n"
d.end-pool->
pool=pool->
intmain()
ngx_pool_t*pool;
--------------------------------\n"
createanewpool:
\n"
pool=ngx_create_pool(1024,NULL);
dump_pool(pool);
allocblock1fromthepool:
ngx_palloc(pool,512);
allocblock2fromthepool: