Nginx源码研究.docx
- 文档编号:29826512
- 上传时间:2023-07-27
- 格式:DOCX
- 页数:60
- 大小:272.36KB
Nginx源码研究.docx
《Nginx源码研究.docx》由会员分享,可在线阅读,更多相关《Nginx源码研究.docx(60页珍藏版)》请在冰豆网上搜索。
Nginx源码研究
Nginx源码研究
概貌3
内存池5
内存分配相关函数5
内存池结构5
相关函数7
小结9
Array10
结构10
相关函数10
Queue11
结构11
相关函数12
Hashtable12
结构12
相关函数14
List15
结构15
相关函数15
Nginx启动处理16
Work进程逻辑(ngx_worker_process_cycle()函数)30
1.进程部份30
2.线程部份31
3.回到进程32
Cycle32
Connection33
connection的内存分布33
connection的分配与回收33
Event34
结构34
相关函数38
Connection38
结构38
相关函数42
Connection与Event42
Bufs44
Upstream46
Nginx的源码是0.8.16版本。
不是最新版本,但是与网上其他人研究nginx的源码有所修改。
阅读时注意参照对比。
概貌
Nginx可以开启多个进程,每个进程拥有最大上限128个子线程以及一定的可用连接数。
如果你希望使用线程可以在配置文件中设置worker_threads这个参数,但这个参数在Nginx官方手册上没有。
只有通过阅读源代码才看到。
最大客户端连接数等于进程数与连接数的乘积,连接是在主进程中初始化的,一开始所有连接处于空闲状态。
每一个客户端请求进来以后会通过事件处理机制,在Linux是Epoll,在FreeBSD下是KQueue放到空闲的连接里。
如果设置了线程数,那么被填充的连接会在子线程中处理,否则会在主线程中依次处理。
nginx由以下几个元素组成:
1.worker(进程)
2.thread(线程)
3.connection(连接)
4.event(事件)
5.module(模块)
6.pool(内存池)
7.cycle(全局设置)
8.log(日志)
整个程序从main()开始算
ngx_max_module=0;
for(i=0;ngx_modules[i];i++){
ngx_modules[i]->index=ngx_max_module++;
}
这几句比较关键,对加载的模块点一下数,看有多少个。
ngx_modules并不是在原代码中被赋值的,你先执行一下./configure命令生成用于编译的make环境。
在根目录会多出来一个文件夹objs,找到ngx_modules.c文件,默认情况下nginx会加载大约30个模块,的确不少,如果你不需要那个模块尽量还是去掉好一些。
接下来比较重要的函数是ngx_init_cycle(),这个函数初始化系统的配置以及网络连接等,如果是多进程方式加载的会继续调用ngx_master_process_cycle(),这是main函数中调用的最关键的两个函数。
ngx_init_cycle()实际上是个复杂的初始化函数,首先是加载各子模块的配置信息、并初始化各组成模块。
任何模块都有两个重要接口组成,一个是create_conf,一个是init_conf。
分别是创建配置和初始化配置信息。
模块按照先后顺序依次初始化,大概是这样的:
内核模块(ngx_core_module),
错误日志(ngx_errlog_module),
配置模块(ngx_conf_module),
事件模块(ngx_events_module),
事件内核模块(ngx_event_core_module),
EPOLL模块(ngx_epoll_module),
http模块(ngx_http_module),
http内核模块(ngx_http_core_module),
http日志模块(ngx_http_log_module),
……
epoll是比较关键的核心模块之一,nginx兼容多种IO控制模型。
内存池
内存分配相关函数
ngx_alloc.c中包括所有nginx内存申请的相关函数。
ngx_alloc()包装了malloc(),仅添加了内存分配失败时的,log输出和debug时的log输出。
ngx_calloc()调用上面的函数,成功分配后,将内存清零。
ngx_memalign()也是向操作系统申请内存,只不过采用内存对齐方式。
估计是为了减少内存碎片。
如果操作系统支持posix_memalign()就采用它,如果支持memalign()则用memalign()。
在0.8.19版本中,作者不再使用ngx_alloc(),而全部改用ngx_memalign()。
注:
在nginx的main()函数中,通过将ngx_pagesize设置为1024来指定内存分配按1024bytes对齐。
这是不是意味着你虽指示分配10bytes的内存,实际上nginx也向操作系统申请至少1024bytes的内存。
内存池结构
Nginx的内存池类型是ngx_pool_t。
这个类型定义在ngx_core.h中。
typedefstructngx_pool_sngx_pool_t;
由定义可知ngx_pool_t背后实际上是structngx_pool_s。
这个结构体在ngx_palloc.h中有定义。
据说以前版本nginx中内存池的结构如下:
structngx_pool_s{
u_char *last;
u_char *end;
ngx_pool_t *current;
ngx_chain_t *chain;
ngx_pool_t *next;
ngx_pool_large_t *large;
ngx_pool_cleanup_t*cleanup;
ngx_log_t *log;
};
目前版本中结构则是这样:
内存池管理结点:
typedefstruct{
u_char*last;/*指向所使用内存的最后的地址*/
u_char*end;/*指向所申请到内存块的最后的地址*/
ngx_pool_t*next;/*指向下一个ngx_pool_t*/
ngx_uint_tfailed;/*这个*/
}ngx_pool_data_t;
内存池管理队列的头结点:
structngx_pool_s{
ngx_pool_data_td;
size_tmax;/*内存池块中空闲空间的大小*/
ngx_pool_t*current;/*指向当前块自身*/
ngx_chain_t*chain;
ngx_pool_large_t*large;/*用来指向大块内存*/
ngx_pool_cleanup_t*cleanup;/*释放内存时调用的清除函数队列*/
ngx_log_t*log;/*指向log的指针*/
};
清除函数的指针如下:
typedefvoid(*ngx_pool_cleanup_pt)(void*data);
清除函数的队列结构:
typedefstructngx_pool_cleanup_sngx_pool_cleanup_t;
structngx_pool_cleanup_s{
ngx_pool_cleanup_pthandler;/*清除函数*/
void*data;/*清除函数所用的参数*/
ngx_pool_cleanup_t*next;/*下一个结点的指针*/
};
指向大块内存的结构:
typedefstructngx_pool_large_sngx_pool_large_t;
structngx_pool_large_s{
ngx_pool_large_t*next;/*指向下一个大块内存。
*/
/*大块内存也是队列管理。
*/
void*alloc;/*指向申请的大块数据*/
};
当待分配空间已经超过了池子自身大小,nginx也没有别的好办法,只好按照你需要分配的大小,实际去调用malloc()函数去分配,例如池子的大小是1K,待分配的大小是1M。
实际上池子里只存储了ngx_pool_large_t结构,这个结构中的alloc指针,指向被分配的内存,并把这个指针返回给系统使用。
相关函数
ngx_create_pool()函数用来创建内存池。
第一步,调用ngx_alloc()申请内存;
第二步,设置ngx_pool_t中的成员d中的各个变量;
…
p->d.last=(u_char*)p+sizeof(ngx_pool_t);
p->d.end=(u_char*)p+size;
…
从代码看出,d.end指向内存块的结尾处,而d.last则指向所占用的内存的结尾处。
刚申请的内存中占用ngx_pool_t结构作为管理单元。
所以,此时d.last指向(u_char*)p+sizeof(ngx_pool_t)处。
第三步,设置其他成员。
注意:
在计算max时,max中存放的数指所申请内存块中空闲的大小。
因此,在计算max之前先减去了管理结点本身的大小。
ngx_destroy_pool()用来释放内存池,一共分三步:
第一步、在释放前先对业务逻辑进行释放前的处理
for(c=pool->cleanup;c;c=c->next){
if(c->handler){
ngx_log_debug1(NGX_LOG_DEBUG_ALLOC,pool->log,0,
"runcleanup:
%p",c);
c->handler(c->data);
}
}
第二步、释放large占用的内存
for(l=pool->large;l;l=l->next){
ngx_log_debug1(NGX_LOG_DEBUG_ALLOC,pool->log,0,"free:
%p",l->alloc);
if(l->alloc){
ngx_free(l->alloc);
}
}
第三步、释放所有的池子
for(p=pool,n=pool->next;/*void*/;p=n,n=n->next){
ngx_free(p);
if(n==NULL){
break;
}
}
ngx_palloc_large()函数专用来申请大块内存。
第一步,申请的大块内存,在ngx_pool_t中大块他队列中寻找空闲的ngx_pool_larger结点。
如果找到,将大块内存挂在该结点上。
ngx_pool_larger队列中查找空闲结点数不会超过三次。
超过三个结点没找到空闲结点就放弃。
创建一个新的结点,将申请到地大块内存挂在这个新结点上。
将这个结点插入队列头部。
ngx_palloc()函数用来申请内存块。
首先要说明的是内存池中可能是由多块内存块组成的队列。
其中每块内存都有一个ngx_pool_t管理结点用来连成管理队列。
1.如果申请的内存大小超过了当前的内存池结点中空闲空间,nginx直接采用ngx_palloc_large()函数进行大块内存申请;
2.在内存池管理队列中寻找能够满足申请大小的管理结点。
如果找到了,先按32位对齐方式计算申请内存的指针,再将last指向申请内存块的尾部。
3.找不到合适的内存池,用ngx_palloc_block()函数。
ngx_pnalloc()函数与ngx_palloc()函数唯一不同之处,就是在计算申请内存的指针的方式未按32位对齐方式计算。
ngx_palloc_block()函数用来分配新的内存池块,形成一个队列。
这个函数中申请到新的内存池块后,在该块中分配完ngx_pool_data_t结点后,将这个结点挂在内存池队列的结尾处。
这个函数中有两个要注意的地方:
1.在内存池块中保留ngx_pool_data_t时,不仅是按ngx_pool_data_t大小计算而且是按32位对齐。
2.ngx_pool_data_t结构中的failed的妙用。
单从字面上不是太好理角这个成员变量的作用。
实际上是用来计数用的。
for(p=current;p->d.next;p=p->d.next){
if(p->d.failed++>4){
current=p->d.next;
}
}
从上面这段代码是寻找内存池队列的尾部。
当队列较长,由于内存池管理队列是单向队列所以每次从头到尾搜索是很费时的。
每次搜寻失败的结点(非尾部结点)的failed加1。
failed指出了该结点经历多少次查寻,目前版本中超过4次时,将内存池的current指针指向其后续的结点。
这样,下次再做类似查询时,可以跳过若干不必要的结点加快查询速度。
ngx_pmemalign()函数采用内存对齐的方式申请大内存块。
ngx_pfree()函数用来释放大内存块。
成功返回NGX_OK,失败返回NGX_DECLINED。
ngx_pcalloc()函数使用ngx_palloc()函数申请内存后,将申请的内存清零。
ngx_pool_cleanup_add()函数只是用来添加内存池的cleanup队。
由ngx_pool_cleanup_t类型的结点组成了内存池的清除函数处理队列。
后加入队列的函数先调用。
Nginx中预定义了两个cleanup函数。
voidngx_pool_cleanup_file(void*data)用来关闭打开的文件。
voidngx_pool_delete_file(void*data)用来删除文件并且试图关闭文件。
小结
下面是nginx内存池的概貌图。
Array
结构
structngx_array_s{
void*elts;/*指向数组元素的起始地址*/
ngx_uint_tnelts;/*现使用的元素的数目*/
size_tsize;/*元素的大小*/
ngx_uint_tnalloc;/*分配数组中元素的数目*/
ngx_pool_t*pool;/*指向所在的内存池的指针*/
};
注:
nelts应该小于等于nalloc。
例:
分配了5个元素,使用了3个元素;则nalloc=5,nelts=3。
相关函数
ngx_array_init()函数,在指定的array对象上分配n个元素,元素大小为size。
ngx_array_create()函数,函数参数中没有array对象的指针。
这个函数在内存池中创建一个array对象,并且分配n个元素,元素大小为size。
ngx_array_push()函数,将array对象当作堆栈,作压栈处理。
如果当前内存池没有空闲空间可用,就会申请新的内存池并且创建一个是原来array对象两倍大小的新array,原array对象中的元素复制到新array中。
ngx_array_push_n()函数,与ngx_array_push()函数功能类似。
ngx_array_push_n()是压n个元素,ngx_array_push()压入一个元素。
Queue
结构
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(哨兵)节点,他指向队列的头和尾
相关函数
ngx_queue_init(()函数,初始化队列;
ngx_queue_insert_head()函数,在队列头部插入一个元素;
ngx_queue_sentinel()函数,取得队列的哨兵;
ngx_queue_prev()函数,取得前一个元素;
ngx_queue_last()函数,取得队列最后一个元素;
ngx_queue_sort()函数,对队列中的元素进行排序;
ngx_destroy_pool()函数,销毁队列。
Hashtable
结构
这个在模块配置解析里经常被用到。
该hash结构是只读的,即仅在初始创建时可以给出保存在其中的key-val对,其后就只能查询而不能进行增删改操作了。
typedefstruct{
void*value;
u_charlen;
u_charname[1];
}ngx_hash_elt_t;
typedefstruct{
ngx_hash_elt_t**buckets;
ngx_uint_tsize;
}ngx_hash_t;
typedefstruct{
ngx_hash_thash;
void*value;
}ngx_hash_wildcard_t;
typedefstruct{
ngx_str_tkey;
ngx_uint_tkey_hash;
void*value;
}ngx_hash_key_t;
typedefngx_uint_t(*ngx_hash_key_pt)(u_char*data,size_tlen);
typedefstruct{
ngx_hash_thash;
ngx_hash_wildcard_t*wc_head;
ngx_hash_wildcard_t*wc_tail;
}ngx_hash_combined_t;
typedefstruct{
ngx_hash_t*hash;
ngx_hash_key_ptkey;
ngx_uint_tmax_size;
ngx_uint_tbucket_size;
char*name;
ngx_pool_t*pool;
ngx_pool_t*temp_pool;
}ngx_hash_init_t;
相关函数
虽然代码理解起来比较混乱,但是使用还是比较简单的,常用的有创建hash和在hash中进行查找两个操作,对于创建hash的操作,过程一般为:
a)构造一个ngx_hash_key_t为成员的数组,包含key,value和使用key计算出的一个hash值
b)构建一个ngx_hash_init_t结构体的变量,其中包含了ngx_hash_t的成员,为hash的结构体,还包括一些其他初始设置,如bucket的大小,内存池等
c)调用ngx_hash_init传入ngx_hash_init_t结构,ngx_hash_key_t的数组,和数组的长度,进行初始化,这样ngx_hash_init_t的hash成员就是我们要的hash结构
查找的过程很简单
a)计算key的hash值;
b)使用ngx_hash_find进行查找,需要同时传入hash值和key,返回的就是value的指针。
List
结构
structngx_list_part_s{
void*elts;/*list中元素*/
ngx_uint_tnelts;/*使用data多少字节,初值为0*/
ngx_list_part_t*next;/*指向下一个listpart结点*/
};
typedefstruct{
ngx_list_part_t*last;/*指向最后一个listpart结点*/
ngx_list_part_tpart;/*list的头结点中listpart部份*/
size_tsize;/*元素大小*/
ngx_uint_tnalloc;/*listpart结点中申请的元素的容量大小*/
ngx_pool_t*pool;/*指向内存池*/
}ngx_list_t;
相关函数
ngx_list_init()函数创建一个新的list对象设置ngx_list_t指针,内存池中申请的头结点大小为n*size。
1.ngx_list_create()函数中没有指定list指针,创建一个新的list对象,头结点大小为n*size。
2.ngx_list_push()函数,将list当作堆栈将元素压栈,如果当前的list结点中没有空间,则在内存池中申请一个新的list结点,且作为堆栈的顶部。
3.遍历一个list的方法
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]...
}
这段示例代码中,part->nelts表示占用的数据区中的字节数,每到一个节点将i设为0;所以i>=part->nelts满足时表示当前list节点中数据处理完了,于是跳到下一个节点,在对下一个节点的数据进行处理之前,再将i设为0。
Nginx启动处理
1.从main()函数开始,进行了一系列的初始化处理工作。
下面将分别介绍,对于不是很重要或是很好理解力的部分可能不作详细说明。
a)首先是从命令行获取参数,打印参数的用法说明。
b)ngx_time_init()函数,获取当前系统的日期和时间。
Nginx中定义了三个全局ngx_str_t变量,ngx_cached_err_log_time、ngx_cached_http
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- Nginx 源码 研究