Nginx模块⾃主开发六:源码剖析配置⽂件解析过程
Nginx源码实现有⼀个很好的优点就是模块化,有点像⾯向对象语⾔的设计模式,实现“⾼内聚,松耦合”,这篇博客主要讲解模
块的共有流程
集中在ngx_cycle.c、 ngx_process.c、 ngx_process_cycle.c 和 ngx_event.c代码中。
共有流程开始于解析 nginx 配置,这个过程集中在 ngx_init_cycle 函数中。 ngx_init_cycle 是 nginx 的⼀个核⼼函数,共有流程中与配置相关的⼏个过程都在这个函数中实现,其中包括解析 nginx 配置、初始化 CORE模块,接着是初始化⽂件句柄,初始化错误⽇志,初始化共享内存,然后是监听端⼝。可以说共有流程 80% 都是现在 ngx_init_cycle 函数中,其中流程可以参考
ngx_cycle_t结构体解析
Nginx框架都是围绕着ngx_cycle_t结构体控制进程运⾏的,ngx_cycle_t结构体初始化就是在ngx_init_cycle函数中。
truct ngx_cycle_s {
/* 保存着所有模块存储配置项的结构体指针,它⾸先是⼀个数组,数组⼤⼩为ngx_max_module,正好与Nginx的module个数⼀样;
每个数组成员⼜是⼀个指针,指向另⼀个存储着指针的数组,因此会看到void ****
每个进程中都有⼀个唯⼀的ngx_cycle_t核⼼结构体,它有⼀个成员conf_ctx维护着所有模块的配置结构体,
其类型是void ****conf_ctx。conf_ctx意义为⾸先指向⼀个成员皆为指针的数组,其中每个成员指针⼜指向另外⼀个
成员皆为指针的数组,第2个⼦数组中的成员指针才会指向各模块⽣成的配置结构体。这正是为了事件模
块、http模块、mail模块⽽设计的,这有利于不同于NGX_CORE_MODULE类型的
特定模块解析配置项。然⽽,NGX_CORE_MODULE类型的核⼼模块解析配置项时,配置项⼀定是全局的,
不会从属于任何{}配置块的,它不需要上述这种双数组设计。解析标识为NGX_DIRECT_CONF类型的配
置项时,会把void****类型的conf_ctx强制转换为void**,也就是说,此时,在conf_ctx指向的指针数组
中,每个成员指针不再指向其他数组,直接指向核⼼模块⽣成的配置鲒构体。因此,NGX_DIRECT_CONF
仅由NGX_CORE_MODULE类型的核⼼模块使⽤,⽽且配置项只应该出现在全局配置中。
*///初始化见ngx_init_cycle,所有为http{} server{} location{}分配的空间都由该指针指向新开辟的空间
//NGX_CORE_MODULE类型模块赋值在ngx_init_cycle
//http{}ngx_http_module相关模块赋值地⽅在ngx_http_block
*/
/*
在核⼼结构体ngx_cycle_t的conf_ctx成员指向的指针数组中,第7个指针由ngx_http_module模块使⽤(ngx_http_module模块的index序号为6,由于由0开始,所以它在ngx_modules数组中排⾏第7。在存放全局配置结构体的conf_ctx数组中,第7个成员指向ngx_http_module模块),这个指针
设置为指向解析http{}块时⽣成的ngx_http_conf_ctx_t结构体,⽽ngx_http_conf_ctx_t的3个成员则分别指向新分配的3个指针数组。新的指针数组中
成员的意义由每个HTTP模块的ctx_index序号指定(ctx_index在HTTP模块中表明它处于HTTP模块间的序号),例如,第6个HTTP模块的ctx_index是5
(ctx_index同样由0开始计数),那么在ngx_http_conf_ctx_t的3个数组中,第6个成员就指向第6个HTTP模块的create_main_conf、create_srv_conf、create_loc_conf⽅法建⽴的结构体,当然,如果相应的回调⽅法没有实现,该指针就为NULL空指针。
*/
/*
见ngx_init_cycle = cycle->conf_ctx; //这样下⾯的ngx_conf_param解析配置的时候,⾥⾯对赋值操作,实际上就是对cycle->conf_ctx[i]可如何由ngx_cycle_t核⼼结构体中到main级别的配置结构体呢?Nginx提供的ngx_http_cycle_get_module_main_conf宏可以实现这个功能
*/
void ****conf_ctx; //有多少个模块就会有多少个指向这些模块的指针,见ngx_init_cycle ngx_max_module
ngx_pool_t *pool; // 内存池
/* ⽇志模块中提供了⽣成基本ngx_log_t⽇志对象的功能,这⾥的log实际上是在还没有执⾏ngx_init_cycle⽅法前,
也就是还没有解析配置前,如果有信息需要输出到⽇志,就会暂时使⽤log对象,它会输出到屏幕。
在ngx_init_cycle⽅法执⾏后,将会根据f配置⽂件中的配置项,构造出正确的⽇志⽂件,此时会对log重新赋值。 */
//ngx_init_cycle中赋值cycle->log = &cycle->new_log;
ngx_log_t *log; //指向ngx_log_init中的ngx_log,如果配置error_log,指向这个配置后⾯的⽂件参数,见ngx_error_log。否则在ngx_log_open_def
/* 由f配置⽂件读取到⽇志⽂件路径后,将开始初始化error_log⽇志⽂件,由于log对象还在⽤于输出⽇志到屏幕,
这时会⽤new_log对象暂时性地替代log⽇志,待初始化成功后,会⽤new_log的地址覆盖上⾯的log指针 */
// 如果没有配置error_log则在ngx_log_open_default设置为NGX_ERROR_LOG_PATH,如果通过error_log有配置过则通过ngx_log_set_log添加到该new_lo /* 全局中配置的error_log xxx存储在ngx_cycle_s->new_log,http{}、server{}、local{}配置的error_log保存在ngx_http_core_loc_conf_t->error_log,
见ngx_log_set_log,如果只配置全局error_log,不配置http{}、server{}、local{}则在ngx_http_core_merge_loc_conf conf->error_log = &cf->cycle->new_log //ngx_log_insert插⼊,在ngx_log_error_core到对应级别的⽇志配置进⾏输出,因为可以配置error_log不同级别的⽇志存储在不同的⽇志⽂件中
ngx_log_t new_log;//如果配置error_log,指向这个配置后⾯的⽂件参数,见ngx_error_log。否则在ngx_log_open_default中设置
ngx_uint_t log_use_stderr; /* unsigned log_use_stderr:1; */
/* 对于poll,rtsig这样的事件模块,会以有效⽂件句柄数来预先建⽴这些ngx_connection t结构
体,以加速事件的收集、分发。这时files就会保存所有ngx_connection_t的指针组成的数组,files_n就是指
针的总数,⽽⽂件句柄的值⽤来访问files数组成员 */
ngx_connection_t **files; //sizeof(ngx_connection_t *) * cycle->files_n 见ngx_event_process_init ngx_get_connection
/*
从图9-1中可以看出,在ngx_cycle_t中的connections和free_connections达两个成员构成了⼀个连接池,其中connections指向整个连
接池数组的⾸部,⽽free_connections则指向第⼀个ngx_connection_t空闲连接。所有的空闲连接ngx_connection_t都以data成员(见9.3.1节)作
为next指针串联成⼀个单链表,如此,⼀旦有⽤户发起连接时就从free_connections指向的链表头获取⼀个空闲的连接,同时free_connections再指
向下⼀个空闲连接。⽽归还连接时只需把该连接插⼊到free_connections链表表头即可。
*///见ngx_event_process_init, ngx_connection_t空间和它当中的读写ngx_event_t存储空间都在该函数⼀次性分配好
ngx_connection_t *free_connections;// 可⽤连接池,与free_connection_n配合使⽤
ngx_uint_t free_connection_n;// 可⽤连接池中连接的总数
//ngx_connection_s中的queue添加到该链表上
/*
通过读操作可以判断连接是否正常,如果不正常的话,就会把该ngx_close_connection->ngx_free_connection释放出来,这样
如果之前free_connections上没有空余ngx_connection_t,c = ngx_cycle->free_connections;就可以获取到刚才释放出来的ngx_connection_t
见ngx_drain_connections
*/
ngx_queue_t reusable_connections_queue;/* 双向链表容器,元素类型是ngx_connection_t结构体,表⽰可重复使⽤连接队列表⽰可以重⽤的连接
//ngx_http_optimize_servers->ngx_http_init_listening->ngx_http_add_listening->ngx_create_listening把解析到的listen配置项信息添加到cycle->listening中
//通过"listen"配置创建ngx_listening_t加⼊到该数组中
ngx_array_t listening;// 动态数组,每个数组元素储存着ngx_listening_t成员,表⽰监听端⼝及相关的参数
/* 动态数组容器,它保存着nginx所有要操作的⽬录。如果有⽬录不存在,就会试图创建,⽽创建⽬录失败就会导致nginx启动失败。 */
//通过解析配置⽂件获取到的路径添加到该数组,例如f中的client_body_temp_path proxy_temp_path,参考ngx_conf_set_path_slot
//这些配置可能设置重复的路径,因此不需要重复创建,通过ngx_add_path检测添加的路径是否重复,不重复则添加到paths中
ngx_array_t paths;//数组成员 nginx_path_t ,
ngx_array_t config_dump;
/* 单链表容器,元素类型是ngx_open_file_t 结构体,它表⽰nginx已经打开的所有⽂件。事实上,nginx框架不会向open_files链表中添加⽂件。
⽽是由对此感兴趣的模块向其中添加⽂件路径名,nginx框架会在ngx_init_cycle ⽅法中打开这些⽂件 */
//该链表中所包含的⽂件的打开在ngx_init_cycle中打开
ngx_list_t open_files; //如f配置⽂件中的access_log参数的⽂件就保存在该链表中,参考ngx_conf_open_file
//创建ngx_shm_zone_t在ngx_init_cycle,在ngx_shared_memory_add也可能创建新的ngx_shm_zone_t,为每个ngx_shm_zone_t真正分配共享内存空间在 ngx_list_t shared_memory;// 单链表容器,元素类型是ngx_shm_zone_t结构体,每个元素表⽰⼀块共享内存
ngx_uint_t connection_n;// 当前进程中所有链接对象的总数,与connections成员配合使⽤
ngx_uint_t files_n; //每个进程能够打开的最多⽂件数赋值见ngx_event_process_init
/*
从图9-1中可以看出,在ngx_cycle_t中的connections和free_connections达两个成员构成了⼀个连接池,其中connections指向整个连接池数组的⾸部,
⽽free_connections则指向第⼀个ngx_connection_t空闲连接。所有的空闲连接ngx_connection_t都以data成员(见9.3.1节)作为next指针串联成⼀个
单链表,如此,⼀旦有⽤户发起连接时就从free_connections指向的链表头获取⼀个空闲的连接,同时free_connections再指向下⼀个空闲连
接。⽽归还连接时只需把该连接插⼊到free_connections链表表头即可。
在connections指向的连接池中,每个连接所需要的读/写事件都以相同的数组序号对应着read_events、write_events读/写事件数组,
相同序号下这3个数组中的元素是配合使⽤的
*/
ngx_connection_t *connections;// 指向当前进程中的所有连接对象,与connection_n配合使⽤
/*
事件是不需要创建的,因为Nginx在启动时已经在ngx_cycle_t的read_events成员中预分配了所有的读事件,并在write_events成员中预分配了所有的写事件
在connections指向的连接池中,每个连接所需要的读/写事件都以相同的数组序号对应着read_events、write_events读/写事件数组,相同序号下这
3个数组中的元素是配合使⽤的。图9-1中还显⽰了事件池,Nginx认为每⼀个连接⼀定⾄少需要⼀个读事件和⼀个写事件,有多少连接就分配多少个读、
写事件。怎样把连接池中的任⼀个连接与读事件、写事件对应起来呢?很简单。由于读事件、写事件、连接池是由3个⼤⼩相同的数组组成,所以根据数组
序号就可将每⼀个连接、读事件、写事件对应起来,这个对应关系在ngx_event_core_module模块的初始化过程中就已经决定了(参见9.5节)。这3个数组的⼤⼩都是由cycle->connection_n决定。
*/
ngx_event_t *read_events;// 指向当前进程中的所有读事件对象,connection_n同时表⽰所有读事件的总数
ngx_event_t *write_events;// 指向当前进程中的所有写事件对象,connection_n同时表⽰所有写事件的总数
/* 旧的ngx_cycle_t 对象⽤于引⽤上⼀个ngx_cycle_t 对象中的成员。例如ngx_init_cycle ⽅法,在启动初期,
需要建⽴⼀个临时的ngx_cycle_t对象保存⼀些变量,
再调⽤ngx_init_cycle ⽅法时就可以把旧的ngx_cycle_t 对象传进去,⽽这时old_cycle对象就会保存这个前期的ngx_cycle_t对象。 */
ngx_cycle_t *old_cycle;
ngx_str_t conf_file;// 配置⽂件相对于安装⽬录的路径名称默认为安装路径下的NGX_CONF_PATH,见ngx_process_options
ngx_str_t conf_param;// nginx 处理配置⽂件时需要特殊处理的在命令⾏携带的参数,⼀般是-g 选项携带的参数
ngx_str_t conf_prefix; // nginx配置⽂件所在⽬录的路径 ngx_prefix 见ngx_process_options
ngx_str_t prefix; //nginx安装⽬录的路径 ngx_prefix 见ngx_process_options
ngx_str_t lock_file;// ⽤于进程间同步的⽂件锁名称
ngx_str_t hostname; // 使⽤gethostname系统调⽤得到的主机名在ngx_init_cycle中⼤写字母被转换为⼩写字母
};
配置解析
配置解析接⼝
ngx_init_cycle 提供的是配置解析接⼝。接⼝是⼀个切⼊点,通过少量代码提供⼀个完整功能的调⽤。配置解析接⼝分为两个阶段,⼀个是准备阶段,另⼀个就是真正开始调⽤配置解析。准备阶段指什么呢?主要是准备三点:
准备内存:nginx会根据 以往的经验(old_cycle)预测这⼀次的配置 需要分配多少内存
if (old_cycle->shared_lts) {
n = old_cycle->shared_lts;
for (part = old_cycle->shared_; part; part = part->next)
{
n += part->nelts;
}
} else {
n = 1;
}
if (ngx_list_init(&cycle->shared_memory, pool, n, sizeof(ngx_shm_zone_t))
!= NGX_OK)
{
ngx_destroy_pool(pool);
return NULL;
}
准备错误⽇志:nginx启动可能出错,出现就要记录就要记录在⽇志⽂件中 。
log = old_cycle->log;
pool = ngx_create_pool(NGX_CYCLE_POOL_SIZE, log);
if (pool == NULL) {
return NULL;
}
pool->log = log;
cycle = ngx_pcalloc(pool, sizeof(ngx_cycle_t));
if (cycle == NULL) {
ngx_destroy_pool(pool);
return NULL;
}
cycle->pool = pool;
cycle->log = log;
cycle->old_cycle = old_cycle;
准备数据结构,⼀个是ngx_cycle_t结构,⼀个是ngx_conf_t结构,ngx_cycle_t结构⽤于存放所有CORE模块的 配置,⽽ngx_conf_t则是⽤于存放解析配置的上下⽂的信息
ngx_conf_t结构体
nginx 配置文件struct ngx_conf_s {
//当前解析到的命令名
char *name;
//当前命令的所有参数
ngx_array_t *args;
//使⽤的cycle
ngx_cycle_t *cycle;
//所使⽤的内存池
ngx_pool_t *pool;
//这个pool将会在配置解析完毕后释放。
ngx_pool_t *temp_pool;
//这个表⽰将要解析的配置⽂件
ngx_conf_file_t *conf_file;
/
/配置log
ngx_log_t *log;
//主要为了提供模块的层次化(后续会详细介绍)
void *ctx;
//模块类型
ngx_uint_t module_type;
//命令类型
ngx_uint_t cmd_type;
//模块⾃定义的handler
ngx_conf_handler_pt handler;
//⾃定义handler的conf
char *handler_conf;
};
for (i = 0; ngx_modules[i]; i++) {
if (ngx_modules[i]->type != NGX_CORE_MODULE) {
continue;
}
module = ngx_modules[i]->ctx;
if (module->create_conf) {
rv = module->create_conf(cycle);
if (rv == NULL) {
ngx_destroy_pool(pool);
return NULL;
}
cycle->conf_ctx[ngx_modules[i]->index] = rv;
}
}
< = cycle->conf_ctx; // cycle是ngx_cycle_t结构,conf就是ngx_conf_t结构
conf.pool = pool;
conf.log = log;
准备好了这些内容,nginx开始调⽤配置解析模块,其代码如下:
if (ngx_conf_param(&conf) != NGX_CONF_OK) {
environ = senv;
ngx_destroy_cycle_pools(&conf);
return NULL;
}
/*开始解析⽂件然后来看ngx_conf_parse,这个函数第⼆个是将要解析的⽂件名,不过这⾥还有⼀个要注意的,那就是第⼆个参数可以为空的,如果为空,则说明if (ngx_conf_parse(&conf, &cycle->conf_file) != NGX_CONF_OK) {
environ = senv;
ngx_destroy_cycle_pools(&conf);
return NULL;
}
第⼀个if解析nginx命令⾏参数’-g’加⼊的配置。第⼆个if解析nginx配置⽂件。好的设计就体现在接⼝极度简化,模块之间的耦合⾮常
低。这⾥只使⽤区区10⾏完成了配置的解析
配置解析
配置解析模块在 ngx_conf_file.c 中实现。模块提供的接⼝函数主要是 ngx_conf_parse,另外模块提供单独的接⼝ngx_conf_param,⽤
来解析命令⾏传递的配置,当然这个接⼝也是 对ngx_conf_parse的包装。
ngx_conf_parse 函数⽀持三种不同的解析环境:
parse_file:解析配置⽂件
parse_block:解析块配置。块配置⼀定是由“ {”和“ }”包裹起来的;
parse_param:解析命令⾏配置。命令⾏配置中不⽀持块指令。
这是⼀个递归的过程。nginx⾸先解析core模块的配置。core模块提供⼀些块指令,这些指令引⼊其他类型的模块,nginx遇到这些
指令,就重新迭代解析过程,解析其他模块的配置。这些模块配置中⼜有⼀些块指令引⼊新的模块类型或者指令类型,nginx就会再次迭代,解析这些新的配置类型。⽐如上图,nginx遇到“events”指令,就重新调⽤ngx_conf_parse()解析event模块配置,解析完
以后ngx_conf_parse()返回,nginx继续解析core模块指令,直到遇到“http”指令。nginx再次调⽤ngx_conf_parse()解析http模块配置的http级指令,当遇到“server”指令时,nginx⼜⼀次调⽤ngx_conf_parse()解析http模块配置的server级指令。
// ngx_conf_parse()解析配置分成两个主要阶段,⼀个是词法分析,⼀个是指令解析。
char *
ngx_conf_parse(ngx_conf_t *cf, ngx_str_t *filename)
{
char *rv;
ngx_fd_t fd;
ngx_int_t rc;
ngx_buf_t buf;
ngx_conf_file_t *prev, conf_file;
enum {
parse_file = 0,
parse_block,
parse_param
} type;
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论