nginx源码阅读(七).nginx的模块化设计
来源:互联网 发布:写算法 编辑:程序博客网 时间:2024/05/29 12:53
前言
高度模块化是nginx的一个特点,在正式进入到具体的模块之前,有必要从整体把握各模块之间联系与nginx对模块的控制。在前面的分析中其实已经使用了模块中提供的方法,比如worker
进程的工作循环中调用的ngx_process_events_and_timers()
函数,用于处理事件。
模块之间的联系
官方提供了5个类型的模块:核心模块、配置模块、事件模块、http模块、mail模块。
- 配置模块主要负责解析
nginx.conf
文件,是其他模块的基础,该类模块中只有一个ngx_conf_module
模块; - 核心模块主要负责定义除配置模块之外的其他模块,该类模块中有6个核心模块。
ngx_mail_module
负责定义mail模块;ngx_http_module
负责定义http模块;ngx_events_module
负责定义事件模块;ngx_core_module
则是nginx启动加载的第一个模块,它主要用来保存全局配置项。ngx_openssl_module
只有当加载了之后,nginx才支持https请求ngx_errlog_module
- 事件模块即负责事件的注册、分发处理、销毁等,该类模块中主要有这几个模块:
ngx_event_core_module
负责加载其他事件模块,是其他事件模块的基础ngx_epoll_module
,该模块则是我们后面需要重点分析的模块,它负责事件的注册、集成、处理等- 其他的模块比如
ngx_kqueue_module
这些我们后面基本不会涉及,就不解释了,毕竟是另外一种I/O多路复用机制,大致的思想是一样的
- http模块和mail模块也无需太多解释,等到后面再介绍。
从中我们可以看出,核心模块中的部分模块与其他模块有一定的联系,比如核心模块中的ngx_event_module
模块,它就作为事件模块的基础,会帮事件模块解析配置项以及存储事件模块中各模块的存储配置项的结构体指针。
这样的设计可以让核心模块尽量的简单,只做一些初始化还有退出的工作,而其他的事情交给相应类型的模块去细做即可。
除了核心模块与其他模块的联系之外,其实各类型模块中的子模块也有联系,同样也以事件模块为例子,在不同的系平台上,I/O多路复用机制可能也不同,比如linux2.6之后的epoll、FreeBSD的kqueue等,那么选择一种合适的机制就成了一个很重要的问题,事件模块中的ngx_event_core_module
就负责这个工作;又比如http模块中,ngx_http_core_module
也负责决定对于不同的请求该选用哪一个http模块来处理。
统一的通用接口
为了将每个模块简单的统一起来,nginx定义了ngx_module_t
结构体作为每个模块的通用接口,同时考虑到灵活性的问题,ngx_module_t
里面只涉及到了模块的初始化还有退出等操作,并且其中的ctx
成员是一个void *
指针,它可以作为任意一种具体类型的模块中的通用性接口,比如在事件模块中,又可以自己再实现一个通用性接口用于事件模块的统一。
还记得ngx_modules
数组吗,它的类型就是ngx_module_t
,即存储了所有模块的ngx_module_t
接口,通过遍历该数组访问统一的接口,就可以做到初始化以及退出,并且通过ctx
可以访问到具体类型模块的通用性接口(比如使用ctx
调用核心模块中所有模块的create_conf
方法)。
下面则是ngx_module_t
的真面目:
typedef struct ngx_module_s ngx_module_t;struct ngx_module_s { //模块在该类模块中的序号 ngx_uint_t ctx_index; //所有模块在ngx_modules数组中的序号 ngx_uint_t index; ngx_uint_t spare0; ngx_uint_t spare1; ngx_uint_t spare2; ngx_uint_t spare3; //模块版本 ngx_uint_t version; //该成员一般指向同一类型模块下的通用性接口 //这样的设计使得模块之间层次分明 //并且支持多种不同类型模块拥有自己特点的接口 //这样的做法称为具体化ngx_module_t接口 void *ctx; //该数组指定了模块处理配置项的方法 ngx_command_t *commands; //模块的类型 //比如配置模块的则是NGX_CONF_MODULE //核心模块的则是NGX_CORE_MODULE ngx_uint_t type; //master进程初始化时使用 //不过我这个版本并没有使用 ngx_int_t (*init_master)(ngx_log_t *log); //模块初始化时使用 ngx_int_t (*init_module)(ngx_cycle_t *cycle); //工作进程初始化时使用 //在nginx初始化的最后阶段会调用 ngx_int_t (*init_process)(ngx_cycle_t *cycle); //初始化/退出线程 //同样并没有使用 ngx_int_t (*init_thread)(ngx_cycle_t *cycle); void (*exit_thread)(ngx_cycle_t *cycle); //工作进程退出时调用 void (*exit_process)(ngx_cycle_t *cycle); //master进程退出时调用 void (*exit_master)(ngx_cycle_t *cycle); //预留成员,并没有使用 uintptr_t spare_hook0; uintptr_t spare_hook1; uintptr_t spare_hook2; uintptr_t spare_hook3; uintptr_t spare_hook4; uintptr_t spare_hook5; uintptr_t spare_hook6; uintptr_t spare_hook7;};
为了加深对nginx中模块化的理解,我们再来回顾一下第二小节中分析的nginx的初始化。其实里面就用到了ngx_core_module
模块,只是当时为了方便讲解以及不一来就以比较抽象的概念来描述初始化过程所以并没有提起。
这里可以以整体架构的角度来考虑nginx的做法,并且再复习一下前面的内容。
ngx_core_module
ngx_module_t
只是整体框架提供给各模块的统一接口,但是每种类型的模块的需求并不一样,比如事件模块中,肯定都需要添加/删除事件等,而http模块中,则需要合并配置项等。因此,ctx
成员的引入,使得不同类型之间的模块可以自己定义自己的接口(根据模块类型的不同具体化ngx_module_t
),我们可以通过type
的不同,使用ctx
成员访问特定的接口,可能有点抽象,举个例子,比如核心模块,它的type
成员为NGX_CORE_MODULE
,我们可以遍历ngx_modules
数组并只访问类型为NGX_CORE_MODULE
的模块,然后通过ctx
调用核心模块特有的create_conf
以及init_conf
方法。
这样设计的结果就是使得模块之前出现了多层次以及多类型化。配置模块很明显是所有模块的基础,而核心模块则是事件模块、http模块、mail模块的基础,事件模块又是http模块、mail模块的基础。
下面是ngx_init_cycle
函数中涉及到核心模块的部分代码:
......//遍历所有模块for (i = 0; ngx_modules[i]; i++) { //若非核心模块直接跳过 if (ngx_modules[i]->type != NGX_CORE_MODULE) { continue; } //获取到核心模块具体化ngx_module_t之后的结构体指针 module = ngx_modules[i]->ctx; //调用核心模块内通用性接口的create_conf方法 //用于创建存储配置项的数据结构 //并且在读取nginx.conf配置文件时,会根据ngx_command_t把解析出来的配置项存放在其中 if (module->create_conf) { rv = module->create_conf(cycle); if (rv == NULL) { ngx_destroy_pool(pool); return NULL; } //conf_ctx是一个四级指针,用于存放指向存储各模块配置项的结构体指针 cycle->conf_ctx[ngx_modules[i]->index] = rv; }}......//调用核心模块中各模块的init_conffor (i = 0; ngx_modules[i]; i++) { //非核心模块直接跳过 if (ngx_modules[i]->type != NGX_CORE_MODULE) { continue; } module = ngx_modules[i]->ctx; if (module->init_conf) { /* 通过ctx访问核心模块中特有的接口 * 之前已经调用过了create_conf方法 * 因此这里将使用解析过的配置项来初始化核心模块 */ if (module->init_conf(cycle, cycle->conf_ctx[ngx_modules[i]->index]) == NGX_CONF_ERROR) { environ = senv; ngx_destroy_cycle_pools(&conf); return NULL; } }}
核心模块的ngx_module_t
中ctx
成员指向的具体化的结构体是ngx_core_module_t
,定义如下:
typedef struct { //核心模块的名称 ngx_str_t name; /* create_conf和init_conf方法的功能上面已经说过,这里不做解释了 */ void *(*create_conf)(ngx_cycle_t *cycle); char *(*init_conf)(ngx_cycle_t *cycle, void *conf);}
ngx_modules数组中各模块的顺序
综合以上所讲解的,可以轻而易举的看出各模块在ngx_modules
的顺序是很重要的。比如ngx_event_module
肯定需要在事件模块初始化前先初始化好,因为ngx_event_module
的排在ngx_modules
数组中的顺序肯定在事件模块中任何一个模块前面,而ngx_event_core_module
模块又肯定在事件模块中的其他模块之前,毕竟它要选择使用哪一种I/O多路复用机制。
如何获取存储模块的配置项结构体指针
nginx的核心结构体ngx_cycle_t
中有一个四级指针成员conf_ctx
,通过它可以获取任意模块的配置项结构体指针。不过由于它是一个四级指针,可能第一次见理解起来不是那么顺利,这样去理解可能要好一些:首先conf_ctx
是一个数组(一个),它存储的元素是指针类型(两个),每个指针又指向了另外的数组(三个),而每个数组又存储的是指针(四个)。这就是为什么conf_ctx
是四级指针。
conf_ctx
的第一级指针(数组),存储的其实是所有核心模块的配置结构体指针,以事件模块为例,通过该指针,就可以得到任意事件模块的配置项结构体指针(通过ngx_module_t
中的ctx_index
编号)(之ngx_event_module
会将所有事件模块的配置项结构体指针形成一个数组)。
这样我们就可以传入想获取配置项结构体指针的模块名,然后通过ngx_module_t
中的index
还有ctx_index
编号来获取配置项结构体指针。
小结
本小节中,主要从整体的角度来分析了模块之间的联系以及nginx对其的高度抽象。除了少量框架代码之外,其余的全都是模块。
有了对模块的整体认识,有助于我们对事件模块进行分析。
- nginx源码阅读(七).nginx的模块化设计
- nginx源码模块化结构
- 【Nginx】模块化设计
- nginx的模块化体系结构
- nginx的模块化体系结构
- Nginx 的模块化体系结构
- nginx源码阅读(一)
- 阅读 Nginx 源码
- 阅读nginx源码_win32
- nginx源码阅读
- 三:深入理解Nginx的模块化 (结合源码详解)
- Nginx的模块化体系介绍
- nginx源码分析6-模块化(1)
- Nginx源码阅读(模块)
- Nginx源码阅读(ngx_pool_t)
- Nginx源码阅读(ngx_queue_t)
- Nginx源码阅读(ngx_list_t)
- Nginx源码阅读(ngx_array_t)
- linux下解压命令大全
- Linux(五) 权限
- 关于Red Hat Enterprise中文乱码问题
- 关于交叉编译log4cplus库的问题
- Tiny6410学习ing—(一)、嵌…
- nginx源码阅读(七).nginx的模块化设计
- 【笔试题】Week05
- 20171103-程序员的自我修养
- Ubuntu部署Django项目
- leetcode题解-44. Wildcard Matching
- bzoj4198 [Noi2015] [荷马史诗] Huffman 编码
- Python处理时间
- BZOJ2038: [2009国家集训队]小Z的袜子(hose)
- 第三届ACM/ICPC新生赛初赛题解