Nginx源码研究.docx

上传人:b****8 文档编号:30489477 上传时间:2023-08-15 格式:DOCX 页数:60 大小:272.36KB
下载 相关 举报
Nginx源码研究.docx_第1页
第1页 / 共60页
Nginx源码研究.docx_第2页
第2页 / 共60页
Nginx源码研究.docx_第3页
第3页 / 共60页
Nginx源码研究.docx_第4页
第4页 / 共60页
Nginx源码研究.docx_第5页
第5页 / 共60页
点击查看更多>>
下载资源
资源描述

Nginx源码研究.docx

《Nginx源码研究.docx》由会员分享,可在线阅读,更多相关《Nginx源码研究.docx(60页珍藏版)》请在冰豆网上搜索。

Nginx源码研究.docx

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

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 解决方案 > 其它

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1