Nginx源代码分析.docx

上传人:b****6 文档编号:6507547 上传时间:2023-01-07 格式:DOCX 页数:24 大小:114.08KB
下载 相关 举报
Nginx源代码分析.docx_第1页
第1页 / 共24页
Nginx源代码分析.docx_第2页
第2页 / 共24页
Nginx源代码分析.docx_第3页
第3页 / 共24页
Nginx源代码分析.docx_第4页
第4页 / 共24页
Nginx源代码分析.docx_第5页
第5页 / 共24页
点击查看更多>>
下载资源
资源描述

Nginx源代码分析.docx

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

Nginx源代码分析.docx

Nginx源代码分析

Nginx源代码分析

1.Nginx代码的目录和结构

nginx的源码目录结构层次明确,从自动编译脚本到各级的源码,层次都很清晰,是一个大型服务端软件构建的一个范例。

以下是源码目录结构说明:

├─auto自动编译安装相关目录

│├─cc针对各种编译器进行相应的编译配置目录,包括Gcc、Ccc等

│├─lib程序依赖的各种库,包括md5,openssl,pcre等

│├─os针对不同操作系统所做的编译配置目录

│└─types

├─conf相关配置文件等目录,包括nginx的配置文件、fcgi相关的配置等

├─contrib

├─htmlindex.html

└─src源码目录

├─core核心源码目录,包括定义常用数据结构、体系结构实现等

├─event封装的事件系统源码目录

├─httphttp服务器实现目录

├─mail邮件代码服务器实现目录

├─misc该目录当前版本只包含googleperftools包

└─osnginx对各操作系统下的函数进行封装以及实现核心调用的目录。

2.基本数据结构

2.1.简单的数据类型

在core/ngx_config.h目录里面定义了基本的数据类型的映射,大部分都映射到c语言自身的数据类型。

typedefintptr_t    ngx_int_t;

typedefuintptr_t   ngx_uint_t;

typedefintptr_t    ngx_flag_t;

其中ngx_int_t,nginx_flag_t,都映射为intptr_t;ngx_uint_t映射为uintptr_t。

这两个类型在/usr/include/stdint.h的定义为:

/*Typesfor`void*'pointers. */

#if__WORDSIZE==64

#ifndef__intptr_t_defined

typedeflongint        intptr_t;

# define__intptr_t_defined

#endif

typedefunsignedlongint   uintptr_t;

#else

#ifndef__intptr_t_defined

typedefint          intptr_t;

# define__intptr_t_defined

#endif

typedefunsignedint      uintptr_t;

#endif

所以基本的操作和整形/指针类型的操作类似。

2.2.字符串的数据类型

nginx对c语言的字符串类型进行了简单的封装, core/ngx_string.h/c 里面包含这些封装的内容。

其中定义了ngx_str_t,ngx_keyval_t,ngx_variable_value_t这几个基础类型的定义如下:

typedefstruct{

  size_t   len;

  u_char  *data;

}ngx_str_t;

 

typedefstruct{

  ngx_str_t key;

  ngx_str_t value;

}ngx_keyval_t;

 

typedefstruct{

  unsigned  len:

28;

  unsigned  valid:

1;

  unsigned  no_cacheable:

1;

  unsigned  not_found:

1;

  unsigned  escape:

1;

  u_char  *data;

}ngx_variable_value_t;

可以看出ngx_str_t在原有的uchar* 的基础上加入的字符串长度的附加信息,初始化使用ngx_string宏进行,他的定义为:

#definengx_string(str)  {sizeof(str)-1,(u_char*)str}

2.3.内存分配相关

(1)系统功能封装

内存相关的操作主要在os/unix/ngx_alloc.{h,c}和core/ngx_palloc.{h,c}下。

其中os/unix/ngx_alloc.{h,c}封装了最基本的内存分配函数,是对c原有的malloc/free/memalign等原有的函数的封装,对应的函数为:

∙ngx_alloc使用malloc分配内存空间

∙ngx_calloc使用malloc分配内存空间,并且将空间内容初始化为0

∙ngx_memalign返回基于一个指定的alignment大小的数值为对齐基数的空间

∙ngx_free对内存的释放操作

(2)Nginx的内存池

为了方便系统模块对内存的使用,方便内存的管理,nginx自己实现了进程池的机制来进行内存的分配和释放,首先nginx会在特定的生命周期帮你统一建立内存池,当需要进行内存分配的时候统一通过内存池中的内存进行分配,最后nginx会在适当的时候释放内存池的资源,开发者只要在需要的时候对内存进行申请即可,不用过多考虑内存的释放等问题,大大提高了开发的效率。

内存池的主要结构为:

//ngx_palloc.h

structngx_pool_s{

  ngx_pool_data_t   d;

  size_t        max;

  ngx_pool_t     *current;

  ngx_chain_t     *chain;

  ngx_pool_large_t  *large;

  ngx_pool_cleanup_t *cleanup;

  ngx_log_t      *log;

};

//ngx_core.h

typedefstructngx_pool_s    ngx_pool_t;

typedefstructngx_chain_s   ngx_chain_t;

下面解释一下主要的几个操作:

//创建内存池

ngx_pool_t*ngx_create_pool(size_tsize,ngx_log_t*log);

大致的过程是创建使用ngx_alloc分配一个size大小的空间,然后将 ngx_pool_t* 指向这个空间,并且初始化里面的成员,其中

p->d.last=(u_char*)p+sizeof(ngx_pool_t);//初始指向ngx_pool_t结构体后面

p->d.end=(u_char*)p+size;//整个结构的结尾后面

p->max=(size

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

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

当前位置:首页 > 幼儿教育

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

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