【ACCESS精品源码栏目提醒】:本文主要为网学会员提供“Nginx源代码目录分析 - 其它资料”,希望对需要Nginx源代码目录分析 - 其它资料网友有所帮助,学习一下!
Nginx 源代码目录分析1. Nginx 代码的目录和结构 nginx 的
源码目录结构层次明确,从自动编译脚本到各级的
源码,层次都很清晰,是一个大型服务端软件构建的一个范例。
以下是
源码目录结构说明:├—auto 自动编译安装相关目录│ ├—cc 针对各种编译器进行相应的编译配置目录,包括 Gcc、Ccc 等│ ├—lib 程序依赖的各种库,包括 md5opensslpcre 等│ ├—os 针对不同操作系统所做的编译配置目录│ └—types├—conf 相关配置文件等目录,包括 nginx 的配置文件、fcgi 相关的配置等├—contrib├—html index.html└—src
源码目录 ├—core 核心
源码目录,包括定义常用数据结构、体系结构实现等 ├—event 封装的事件系统
源码目录 ├—http http 服务器实现目录 ├—mail 邮件代码服务器实现目录 ├—misc 该目录当前版本只包含 google perftools 包 └—os nginx 对各操作系统下的函数进行封装以及实现核心调用的目录。
2. 基本数据结构2.1. 简单的数据类型 在 core/ngx_config.h 目录里面定义了基本的数据类型的映射,大部分都映射到 c 语言自身的数据类型。
typedef intptr_t ngx_int_t typedef uintptr_t ngx_uint_t typedef intptr_t ngx_flag_t 其中 ngx_int_t,nginx_flag_t,都映射为 intptr_t; ngx_uint_t 映射为uintptr_t。
这两个类型在/usr/include/stdint.h 的定义为: / Types for void pointers. / if __WORDSIZE 64 ifndef __intptr_t_defined typedef long int intptr_t define __intptr_t_defined endif typedef unsigned long int uintptr_t else ifndef __intptr_t_defined typedef int intptr_t define __intptr_t_defined endif typedef unsigned int uintptr_t endif 所以基本的操作和整形/指针类型的操作类似。
2.2. 字符串的数据类型 nginx 对 c 语言的字符串类型进行了简单的封装, core/ngx_string.h/c 里面包含这些封装的内容。
其中定义了 ngx_str_t,ngx_keyval_t, ngx_variable_value_t 这几个基础类型的定义如下: typedef struct size_t len u_char data ngx_str_t typedef struct ngx_str_t key ngx_str_t value ngx_keyval_t typedef struct 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 宏进行,他的定义为: define ngx_stringstr sizeofstr - 1 u_char str 2.3. 内存分配相关1 系统功能封装 内存相关的操作主要在 os/unix/ngx_alloc.hc 和 core/ngx_palloc.hc 下。
其中 os/unix/ngx_alloc.hc 封装了最基本的内存分配函数,是对 c 原有的 malloc/free/memalign 等原有的函数的封装对应的函数为: ngx_alloc 使用 malloc 分配内存空间 ngx_calloc 使用 malloc 分配内存空间,并且将空间内容初始化为 0 ngx_memalign 返回基于一个指定的 alignment 大小的数值为对齐基数的空间 ngx_free 对内存的释放操作2 Nginx 的内存池 为了方便系统模块对内存的使用,方便内存的管理,nginx 自己实现了进程池的机制来进行内存的分配和释放, 首先 nginx 会在特定的生命周期帮你统一建立内存池,当需要进行内存分配的时候统一通过内存池中的内存进行分配,最后 nginx 会在适当的时候释放内存池的资源,开发者只要在需要的时候对内存进行申请即可,不用过多考虑内存的释放等问题,大大提高了开发的效率。
内存池的主要结构为: //ngx_palloc.h struct ngx_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 typedef struct ngx_pool_s ngx_pool_t typedef struct ngx_chain_s ngx_chain_t 下面解释一下主要的几个操作: // 创建内存池 ngx_pool_t ngx_create_poolsize_t size ngx_log_t log 大致的过程是创建使用 ngx_alloc 分配一个 size 大小的空间 然后将 ngx_pool_t 指向这个空间, 并且初始化里面的成员 其中 p-gtd.last u_char p sizeofngx_pool_t // 初始指向 ngx_pool_t 结构 体后面 p-gtd.end u_char p size // 整个结构的结尾后面 p-gtmax size lt NGX_MAX_ALLOC_FROM_POOL size : NGX_MAX_ALLOC_FROM_POOL // 最大不超过 NGX_MAX_ALLOC_FROM_POOL也就是 getpagesize-1 大小 其他大都设置为 null 或者 0 // 销毁内存池 void ngx_destroy_poolngx_pool_t pool 遍历链表,所有释放内存,其中如果注册了 clenup也是一个链表结构),会一次调用 clenup 的 handler 进行清理。
// 重置内存池 void ngx_reset_poolngx_pool_t pool 释放所有 large 段内存, 并且将 d-gtlast 指针重新指向 ngx_pool_t 结构之后(和创建时一样) // 从内存池里分配内存 void ngx_pallocngx_pool_t pool size_t size void ngx_pnallocngx_pool_t pool size_t size void ngx_pcallocngx_pool_t pool size_t size void ngx_pmemalignngx_pool_t pool size_t size size_t alignment ngx_palloc 的过程一般为,首先判断待分配的内存是否大于 pool-gtmax的大小,如果大于则使用 ngx_palloc_large 在 large 链表里分配一段内存并返回, 如果小于测尝试从链表的 pool-gtcurrent 开始遍历链表,尝试找出一个可以分配的内存,当链表里的任何一个节点都无法分配内存的时候,就调用ngx_palloc_block 生成链表里一个新的节点, 并在新的节点里分配内存并返回, 同时, 还会将 pool-gtcurrent 指针指向新的位置(从链表里面 pool-gtd.failed 小于等于 4 的节点里找出) ,其他几个函数也基本上为ngx_palloc 的变种,实现方式大同小异 // 释放指定的内存 ngx_int_t ngx_pfreengx_pool_t pool void p这个操作只有在内存在 large 链表里注册的内存在会被真正释放,如果分配的是普通的内存,则会在 destory_pool 的时候统一释放. // 注册 cleanup 回叫函数(结构体) ngx_pool_cleanup_t ngx_pool_cleanup_addngx_pool_t p size_t size 这个过程和我们之前经常使用的有些区别, 他首先在传入的内存池中分配这个结构的空间(包括 data 段), 然后将为结构体分配的空间返回, 通过操作返回的 ngx_pool_cleanup_t 结构来添加回叫的实现。
这个过程在 nginx里面出现的比较多,也就是 xxxx_add 操作通常不是实际的添加操作,而是分配空间并返回一个指针,后续我们还要通过操作指针指向的空间来实现所谓的add2.4. Nginx 的基本容器1 ngx_array 对应的文件为 core/ngx_array.ch ngx_array 是 nginx 内部封装的使用 ngx_pool_t 对内存池进行分配的数组容器,其中的数据是在一整片内存区中连续存放的。
更新数组时只能在尾部压入 1 个或多个元素。
数组的实现结构为: struct ngx_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_createngx_pool_t p ngx_uint_t n size_t size // 销毁数组容器 void ngx_array_destroyngx_array_t a // 将新的元素加入数组容器 void ngx_array_pushngx_array_t a void ngx_array_push_nngx_array_t a ngx_uint_t n //返回n个元素的 指针 这里需要注意的是,和之前的 ngx_pool_cleanup_add 一样,ngx_array_push 只是进行内存分配的操作,我们需要对返回的指针指向的地址进行赋值等操作来实现实际数组值的添加。
具体一点的 push 操作的实现为: 1. 首先判断 nalloc 是否和 nelts 相等,即数组预先分配的空间已经满了, 如果没满则计算地址直接返回指针 2. 如果已经满了则先判断是否我们的 pool 中的当前链表节点还有剩余的空 间,如果有则直接在当前的 pool 链表节点中分配内存,并返回 3. 如果当前链表节点没有足够的空间则使用 ngx_palloc 重新分配一个 2 4. 于之前数组空间大小的数组,然后将数据转移过来,并返回新地址的指 针2 ngx_queue ngx_queue.ch 实现了一个队列的操作逻辑,队列的基本结构为一个双向队列 基础的数据结构为: typedef struct ngx_queue_s ngx_queue_t struct ngx_queue_s ngx_queue_t prev ngx_queue_t next 注意 nginx 的队列操作和结构只进行指针的操作,不负责节点内容空间的分配和保存,所以在定义自己的队列节点的时候,需要自己定义数据结构以及分配空间, 并包含一个 ngx_queue_t 类型的成员 需要获得原始的数据节点的时候需要使用 ngx_queue_data 宏: define ngx_queue_dataq type link type u_char q - offsetoftype link 另外,整个 queue 结构中包含一个 sentinel(哨兵) 节点, 他指向队列的头和尾。
3 ngx_hash ngx_hash.ch 实现了 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_createngx_list_init 和 ngx_list_push 等建立,初始化,添加)操作, 但是对于我们来说最常用的是遍历操作, 下面是 nginx 的注释里面提到的遍历的例子 part amplist.part data part-gtelts for i 0 i if i gt part-gtnelts if part-gtnext NULL break part part-gtnext data part-gtelts i 0 ... datai ... 5 ngx_buf 对应的文件为 core/ngx_buf.ch buf 分为两种类型一种是 file一种是 memory.因此这里会有文件的一些操作域。
可以看到 buf 相对于 pool 多了一个 pos 域file_pos.这里我们要知道我们发送往套接字异或者其他的设备,我们这里会现将数据放到 buf 中,然后当设备或者套接字准备好了,我们就会从 buf 中读取,因此这里 pos 指针就是放到 buf 中的已经被执行的数据也就是已经送往套接字的位置。
struct ngx_buf_s ///pos 表示已经执行的数据的位置。
u_char pos ///last 和上面内存池中 last 一样,也就是使用的内存的最后一个字节的指针 u_char last///文件指针 off_t file_pos off_t file_last///buf 的开始指针 u_char start / start of buffer / u_char end / end of buffer ////这里表示这个 buf 从属于那个模块。
ngx_buf_tag_t tag ngx_file_t file ngx_buf_t shadow///一些标记 / the bufs content could be changed / unsigned temporary:1///在内存中是不能改变的。
unsigned memory:1///是否是 mmap 的内存 unsigned mmap:1 unsigned recycled:1///是否文件。
unsigned in_file:1 unsigned flush:1 unsigned sync:1 unsigned last_buf:1 unsigned last_in_chain:1 unsigned last_shadow:1 unsigned temp_file:1 / STUB / int num3. nginx 的 core module 的结构和运行机制3.1. ngx_init_cycle 其中一个比较重要的函数调用是, ngx_init_cycle 这个是使用 kscope输出的他的调用关系,他被 mainngx_master_process_cyclengx_single_process_cycle 调用, 其中后两者是在 reconfigure 的时候被调用的 他主要做了如下几件事情: 初始化 cycle 是基于旧有的 cycle 进行的,比如这里的 init_cycle,会继承 old cycle 的很多属性, 比如 log 等, 但是同时会对很多资源重新分配比如 pool shared mem file handler listening socket 等,同时清除旧有的 cycle 的资源 另外,ngx_master/single_process_cycle 里面会对 init_process 进行调用, 并且循环调用 ngx_process_events_and_timers , 其中里面会调用ngx_process_eventscycle timer flags 对事件循环进行 polliing 时间一般默认为 500 ms。
Nginx 的 OS module 的结构和运行机制4. nginx 的 http module 的结构和运行机制 HTTP 相关的 Module 都在 src/http 目录和其子目录下, 其中 src/http下的文件为 http 模块的核心文件, src/http/modules 下的文件为 http 模块的扩展模块。
4.1. ngx_http.ch ngx_http.c 中,注册了 http 这个指令的处理模块,对应ngx_http_block 函数 static ngx_command_t ngx_http_commands ngx_stringquothttpquot NGX_MAIN_CONFNGX_CONF_BLOCKNGX_CONF_NOARGS ngx_http_block 0 0 NULL ngx_null_command 这个函数里面会进行一些 conf 资源分配/Merge,配置文件解析等工作。
这里面有个一比较重要的工作是注册了 nginx http 的 phase handler if ngx_http_init_phase_handlerscf cmcf NGX_OK return NGX_CONF_ERROR phase handler 的类型在 ngx_http_core_module 这里定义: typedef enum 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 或者read event 的改写,其中在 ngx_http_core_content_phase 里面, 有对location handler 的调用, 其中的 r-gtcontent_handler 就是运行时刻从location handler 中注册的, if r-gtcontent_handler r-gtwrite_event_handler ngx_http_request_empty_handler ngx_http_finalize_requestr r-gtcontent_handlerr /实际的请求发 送处理/ return NGX_OK 其中, 在各个 phase 的结束阶段,一般都是调用 r-gtphase_handler return NGX_AGAIN 移动 request 中 phase_handler 的指针,并且示意主程序继续进行。
这里,无论是 phase handler还是 location handler,我们都是可以在程序里进行注册的。
另外, ngx_http_block 里面调用了 ngx_http_optimize_servers ,这个函数对 listening 和 connection 相关的变量进行了初始化和调优,并最终在ngx_http_add_listening (被 ngx_http_add_listening 调用) 中注册了listening 的 handler 为 ngx_http_init_connection ls-gthandler ngx_http_init_connection ngx_http_init_connection 在 ngx_http_request.c 中定义,后续会进行详细的介绍。
4.2. ngx_http_request.ch 这里面,ngx_http_init_connection 注册了 connection 事件的读操作的回叫函数, 并将写操作设置为空函数 rev c-gtread rev-gthandler ngx_http_init_request c-gtwrite-gthandler ngx_http_empty_handler 当新的连接进入的时候,就执行到 ngx_http_init_request 开始对后面的流程进行处理,主要是将 rev 的 handler 设置为ngx_http_process_request_line , 然后 ngx_http_process_request_line会先后有调度到 ngx_http_process_request_headers 和ngx_http_process_request 函数对读取过来的 event 进行处理,其中,ngx_http_process_request_headers 里面会对 http 的请求头进行解析,ngx_http_process_request 设置 event handler 到ngx_http_request_handler ,ngx_http_request_handler 中会根据事件可能是读取还是写入的操作分别调用 request 的 read_event_handler 和write_event_handler 所以后续程序对 request 的read/writeevent_handler 调整 本质上类似对 rev 和 wev 的 handler 的调整,只是回叫函数的参数变更为了 ngx_request_t 而不是之前的ngx_event_t c-gtread-gthandler ngx_http_request_handler c-gtwrite-gthandler ngx_http_request_handler r-gtread_event_handler ngx_http_block_reading 根据上面代码可以看出, 模块开始使用 ngx_http_block_reading 这个handler 对后续的读请求进行处理 在注册完事件后, ngx_http_process_request 会分别调用下面的两个函数 ngx_http_handlerr ngx_http_run_posted_requestsc 其中, ngx_http_handler 在 ngx_http_core_module 中定义,处理程序的主请求, ngx_http_run_posted_requests 在 ngx_http_request.c 里定义,处理所有提交的子请求数据的输出。
4.3. ngx_http_core_module.ch 对于 ngx_http_core_module 是 http 模块中比较重要的模块, 他本身是一个 NGX_HTTP_MODULE (不同于 ngx_http_modulengx_http_module 本质上是一个 NGX_CORE_MODULE。
这里面对 http block 下面的一些指令进行了处理, 比如 serverlocation 等, 同时, 上面提到的 ngx_http_handler 也在这里面。
ngx_http_handler 所作的最核心的工作就是在最后调用 并.