size:
NGX_MAX_ALLOC_FROM_POOL;//最大不超过NGX_MAX_ALLOC_FROM_POOL,也就是getpagesize()-1大小
其他大都设置为null或者0
//销毁内存池
voidngx_destroy_pool(ngx_pool_t*pool);
遍历链表,所有释放内存,其中如果注册了clenup(也是一个链表结构),会一次调用clenup的handler进行清理。
//重置内存池
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的过程一般为,首先判断待分配的内存是否大于pool->max的大小,如果大于则使用ngx_palloc_large在large链表里分配一段内存并返回,如果小于测尝试从链表的pool->current开始遍历链表,尝试找出一个可以分配的内存,当链表里的任何一个节点都无法分配内存的时候,就调用ngx_palloc_block生成链表里一个新的节点,并在新的节点里分配内存并返回,同时,还会将pool->current指针指向新的位置(从链表里面pool->d.failed小于等于4的节点里找出),其他几个函数也基本上为ngx_palloc的变种,实现方式大同小异
//释放指定的内存
ngx_int_tngx_pfree(ngx_pool_t*pool,void*p);
这个操作只有在内存在large链表里注册的内存在会被真正释放,如果分配的是普通的内存,则会在destory_pool的时候统一释放.
//注册cleanup回叫函数(结构体)
ngx_pool_cleanup_t*ngx_pool_cleanup_add(ngx_pool_t*p,size_tsize);
这个过程和我们之前经常使用的有些区别,他首先在传入的内存池中分配这个结构的空间(包括data段),然后将为结构体分配的空间返回,通过操作返回的ngx_pool_cleanup_t结构来添加回叫的实现。
(这个过程在nginx里面出现的比较多,也就是xxxx_add操作通常不是实际的添加操作,而是分配空间并返回一个指针,后续我们还要通过操作指针指向的空间来实现所谓的add)
2.4.Nginx的基本容器
(1)ngx_array
对应的文件为core/ngx_array.{c|h}
ngx_array是nginx内部封装的使用ngx_pool_t对内存池进行分配的数组容器,其中的数据是在一整片内存区中连续存放的。
更新数组时只能在尾部压入1个或多个元素。
数组的实现结构为:
structngx_array_s{
void *elts;
ngx_uint_t nelts;
size_t size;
ngx_uint_t nalloc;
ngx_pool_t *pool;
};
其中elts为具体的数据区域的指针,nelts为数组实际包含的元素数量,size为数组单个元素的大小,nalloc为数组容器预先(或者重新)分配的内存大小,pool为分配基于的内存池
常用的操作有:
//创建一个新的数组容器
ngx_array_t*ngx_array_create(ngx_pool_t*p,ngx_uint_tn,size_tsize);
//销毁数组容器
voidngx_array_destroy(ngx_array_t*a);
//将新的元素加入数组容器
void*ngx_array_push(ngx_array_t*a);
void*ngx_array_push_n(ngx_array_t*a,ngx_uint_tn); //返回n个元素的指针
这里需要注意的是,和之前的ngx_pool_cleanup_add一样,ngx_array_push只是进行内存分配的操作,我们需要对返回的指针指向的地址进行赋值等操作来实现实际数组值的添加。
具体一点的push操作的实现为:
1.首先判断 nalloc是否和nelts相等,即数组预先分配的空间已经满了,如果没满则计算地址直接返回指针
2.如果已经满了则先判断是否我们的pool中的当前链表节点还有剩余的空间,如果有则直接在当前的pool链表节点中分配内存,并返回
3.如果当前链表节点没有足够的空间则使用ngx_palloc重新分配一个2倍于之前数组空间大小的数组,然后将数据转移过来,并返回新地址的指针
(2)ngx_queue
ngx_queue.{c,h}实现了一个队列的操作逻辑,队列的基本结构为一个双向队列
基础的数据结构为:
typedefstructngx_queue_s ngx_queue_t;
structngx_queue_s{
ngx_queue_t *prev;
ngx_queue_t *next;
};
注意nginx的队列操作和结构只进行指针的操作,不负责节点内容空间的分配和保存,所以在定义自己的队列节点的时候,需要自己定义数据结构以及分配空间,并包含一个ngx_queue_t类型的成员,需要获得原始的数据节点的时候需要使用ngx_queue_data宏:
#definengx_queue_data(q,type,link) \
(type*)((u_char*)q-offsetof(type,link))
另外,整个queue结构中包含一个sentinel(哨兵)节点,他指向队列的头和尾。
(3)ngx_hash
ngx_hash.{c|h}实现了nginx里面比较重要的一个hash结构,这个在模块配置解析里经常被用到。
该hash结构是只读的,即仅在初始创建时可以给出保存在其中的key-val对,其后就只能查询而不能进行增删改操作了。
下面是简单hash结构的内存布局:
虽然代码理解起来比较混乱,但是使用还是比较简单的,常用的有创建hash和在hash中进行查找两个操作,对于创建hash的操作,过程一般为:
1.构造一个ngx_hash_key_t为成员的数组,包含key,value和使用key计算出的一个hash值
2.构建一个ngx_hash_init_t结构体的变量,其中包含了ngx_hash_t的成员,为hash的结构体,还包括一些其他初始设置,如bucket的大小,内存池等
3.调用ngx_hash_init传入ngx_hash_init_t结构,ngx_hash_key_t的数组,和数组的长度,进行初始化,这样ngx_hash_init_t的hash成员就是我们要的hash结构
查找的过程很简单
1.计算key的hash值
2.使用ngx_hash_find进行查找,需要同时传入hash值和key,返回的就是value的指针
需要注意的是,nginx的hash在查找时使用的是分桶后线性查找法,因此当分桶数确定时查找效率同其中的总key-val对数量成反比。
(4)ngx_list
ngx_list的结构并不复杂,ngx为我们封装了ngx_list_create,ngx_list_init,和ngx_list_push等(建立,初始化,添加)操作,但是对于我们来说最常用的是遍历操作,下面是nginx的注释里面提到的遍历的例子
part=&list.part;
data=part->elts;
for(i=0;;i++){
if(i>=part->nelts){
if(part->next==NULL){
break;
}
part=part->next;
data=part->elts;
i=0;
}
... data[i]...
}
(5)ngx_buf
对应的文件为core/ngx_buf.{c|h}
buf分为两种类型,一种是file,一种是memory.因此这里会有文件的一些操作域。
可以看到buf相对于pool多了一个pos域(file_pos).这里我们要知道我们发送往套接字异或者其他的设备,我们这里会现将数据放到buf中,然后当设备或者套接字准备好了,我们就会从buf中读取,因此这里pos指针就是放到buf中的已经被执行的数据(也就是已经送往套接字)的位置。
structngx_buf_s{
///pos表示已经执行的数据的位置。
u_char*pos;
///last和上面内存池中last一样,也就是使用的内存的最后一个字节的指针
u_char*last;
///文件指针
off_tfile_pos;
off_tfile_last;
///buf的开始指针
u_char*start;/*startofbuffer*/
u_char*end;/*endofbuffer*/
///这里表示这个buf从属于那个模块。
ngx_buf_tag_ttag;
ngx_file_t*file;
ngx_buf_t*shadow;
///一些标记
/*thebuf'scontentcouldbechanged*/
unsignedtemporary:
1;
///在内存中是不能改变的。
unsignedmemory:
1;
///是否是mmap的内存
unsignedmmap:
1;
unsignedrecycled:
1;
///是否文件。
unsignedin_file:
1;
unsignedflush:
1;
unsignedsync:
1;
unsignedlast_buf:
1;
unsignedlast_in_chain:
1;
unsignedlast_shadow:
1;
unsignedtemp_file:
1;
/*STUB*/intnum;
};
3.nginx的coremodule的结构和运行机制
3.1.ngx_init_cycle
其中一个比较重要的函数调用是,ngx_init_cycle,这个是使用kscope输出的他的调用关系,他被main,ngx_master_process_cycle,ngx_single_process_cycle调用,其中后两者是在reconfigure的时候被调用的
他主要做了如下几件事情:
初始化cycle是基于旧有的cycle进行的,比如这里的init_cycle,会继承oldcycle的很多属性,比如log等,但是同时会对很多资源重新分配,比如pool,sharedmem,filehandler,listeningsocket等,同时清除旧有的cycle的资源
另外,ngx_master/single_process_cycle里面会对init_process进行调用,并且循环调用ngx_process_events_and_timers,其中里面会调用ngx_process_events(cycle,timer,flags);对事件循环进行polliing时间一般默认为500ms。
Nginx的OSmodule的结构和运行机制
4.nginx的httpmodule的结构和运行机制
HTTP相关的Module都在src/http目录和其子目录下,其中src/http下的文件为http模块的核心文件,src/http/modules下的文件为http模块的扩展模块。
4.1.ngx_http.[c|h]
ngx_http.c中,注册了http这个指令的处理模块,对应ngx_http_block函数
staticngx_command_t ngx_http_commands[]={
{ngx_string("http"),
NGX_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_NOARGS,
ngx_http_block,
0,
0,
NULL},
ngx_null_command
};
这个函数里面会进行一些conf资源分配/Merge,配置文件解析等工作。
这里面有个一比较重要的工作是注册了nginxhttp的phasehandler
if(ngx_http_init_phase_handlers(cf,cmcf)!
=NGX_OK){
returnNGX_CONF_ERROR;
}
phasehandler的类型在ngx_http_core_module这里定义:
typedefenum{
NGX_HTTP_POST_READ_PHASE=0,
NGX_HTTP_SERVER_REWRITE_PHASE,
NGX_HTTP_FIND_CONFIG_PHASE,
NGX_HTTP_REWRITE_PHASE,
NGX_HTTP_POST_REWRITE_PHASE,
NGX_HTTP_PREACCESS_PHASE,
NGX_HTTP_ACCESS_PHASE,
NGX_HTTP_POST_ACCESS_PHASE,
NGX_HTTP_TRY_FILES_PHASE,
NGX_HTTP_CONTENT_PHASE,
NGX_HTTP_LOG_PHASE
}ngx_http_phases;
每一个phase的handlers都是一个数组,里面可以包含多个元素,通过ngx_array_push添加新的handler。
其中每个phase的处理大都包含了对ngx_request_t的write或者readevent的改写,其中在ngx_http_core_content_phase里面,有对locationhandler的调用,其中的r->content_handler就是运行时刻从locationhandler中注册的,
if(r->content_handler){
r->write_event_handler=ngx_http_request_empty_handler;
ngx_http_finalize_request(r,r->content_handler(r));/*实际的请求发送处理*/
returnNGX_OK;
}
其中,在各个phase的结束阶段,一般都是调用
r->phase_handler++;
r