在c中void*既可以赋值给任何指针,也可以被任何指针赋值。
在c++中void*可以接收所有类型的指针,但是反之不可。
struct ngx_cycle_s {
void ****conf_ctx;
}
if (cmd->type & NGX_DIRECT_CONF) {
conf = ((void **) cf->ctx)[cf->cycle->modules[i]->index];
} else if (cmd->type & NGX_MAIN_CONF) {
conf = &(((void **) cf->ctx)[cf->cycle->modules[i]->index]);
} else if (cf->ctx) {
confp = *(void **) ((char *) cf->ctx + cmd->conf);
if (confp) {
conf = confp[cf->cycle->modules[i]->ctx_index];
}
}
rv = cmd->set(cf, cmd, conf);
首先nginx.conf有三种不同位置的配置文件
#define NGX_DIRECT_CONF 0x00010000
#define NGX_MAIN_CONF 0x01000000
#define NGX_ANY_CONF 0x1F000000
(一)nginx.conf 文件最外层的配置项解析
{ ngx_string("daemon"),
NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_FLAG,
ngx_conf_set_flag_slot,
首先要理解这三个参数,有NGX_DIRECT_CONF宏就一定有NGX_MAIN_CONF宏,有NGX_MAIN_CONF却不一定有NGX_DIRECT_CONF。NGX_DIRECT_CONF的选项都是用模块自己分配的结构体存储的,比如上图的daemon变量是存储在ngx_core_conf_t中,也就是模块的某个结构体有create_conf 函数。
而且NGX_DIRECT_CONF类型的配置最先被读取,在ngix_init_cycle 函数里,
cycle->conf_ctx = ngx_pcalloc(pool, ngx_max_module * sizeof(void *));
conf_ctx它本身占用四个字节,这四个字节存放的是一个一维数组的地址,也就是数组首元素的地址。
下面这个代码是把NGX_CORE_MODULE的存储配置项结构体(ngx_core_module的配置项结构体是ngx_core_conf_t)的指针存储在conf_ctx[]中。
for (i = 0; cycle->modules[i]; i++) {
if (cycle->modules[i]->type != NGX_CORE_MODULE) {
continue;
}
module = cycle->modules[i]->ctx;
if (module->create_conf) {
rv = module->create_conf(cycle);
if (rv == NULL) {
ngx_destroy_pool(pool);
return NULL;
}
cycle->conf_ctx[cycle->modules[i]->index] = rv;
}
}
再接下来,这里的conf结构体的cmd_type为什么是NGX_MAIN_CONF 呢?因为ngx_conf_parse()里面的ngx_conf_handler()提取配置项结构体的代码如下
if (cmd->type & NGX_DIRECT_CONF) {
conf = ((void **) cf->ctx)[cf->cycle->modules[i]->index];
} else if (cmd->type & NGX_MAIN_CONF)
先判断是否含有NGX_DIRECT_CONF,所以这里配置为NGX_MAIN_CONF。
conf.ctx = cycle->conf_ctx;
conf.cycle = cycle;
conf.pool = pool;
conf.log = log;
conf.module_type = NGX_CORE_MODULE;
conf.cmd_type = NGX_MAIN_CONF;
接下来ngx_conf_parse()->ngx_conf_handler(ngx_conf_t *cf, ngx_int_t last),
if (cmd->type & NGX_DIRECT_CONF) {
conf = ((void **) cf->ctx)[cf->cycle->modules[i]->index];
}
conf.ctx = cycle->conf_ctx;所以cf->ctx的值就是conf_ctx的值,原型是void ****conf_ctx;
如果解析到daemon,如何取得ngx_core_conf_t结构体的地址呢,cf->ctx是指向数据的指针,那么[ ]相当于取数组里的成员,因为成员是一级指针*,所以就需要把cf->ctx强转为(void **),表示cf->ctx是一个指针,它指向的类型是void *, conf = ((void **) cf->ctx)[cf->cycle->modules[i]->index],所以conf的值就是ngx_core_conf_t的地址了。
此时会解析到direct和main的,direct前面讲过了,main就是 http{} rtmp{} events{}这样的类型。
(二)nginx.conf MAIN 配置项解析
前一小节说了DIRECT级别的配置项解析,这一节说下MAIN 级别的。
static ngx_command_t ngx_http_commands[] = {
{ ngx_string("http"),
NGX_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_NOARGS,
ngx_http_block,
}
nginx.conf 配置文件里的http{ }是被当成NGX_MAIN_CONF的选项来解析的。 当解析配置文件的程序遇到http{}的时候,就会调用static char * ngx_http_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) {
static char *
ngx_http_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
char *rv;
ngx_uint_t mi, m, s;
ngx_conf_t pcf;
ngx_http_module_t *module;
ngx_http_conf_ctx_t *ctx;
ngx_http_core_loc_conf_t *clcf;
ngx_http_core_srv_conf_t **cscfp;
ngx_http_core_main_conf_t *cmcf;
if (*(ngx_http_conf_ctx_t **) conf) {
return "is duplicate";
}
/* the main http context */
ctx = ngx_pcalloc(cf->pool, sizeof(ngx_http_conf_ctx_t));
if (ctx == NULL) {
return NGX_CONF_ERROR;
}
*(ngx_http_conf_ctx_t **) conf = ctx;
这里的形参 void *conf 代表什么呢?
http{}这个配置项所在的模块是ngx_http_module,它是一个核心模块,NGX_CORE_MODUEL。它是在上节所述的,的, conf_ctx指向一个由所有核心模块的配置项指针组成的数组,解析代码执行到http{}的时候,会找到这个配置项是NGX_MAIN_CONF级别的,然后通过如下两行代码把这个核心模块的配置项结构体的指针取出来。
} else if (cmd->type & NGX_MAIN_CONF) {
conf = &(((void **) cf->ctx)[cf->cycle->modules[i]->index]);
为什么要(void **)上节说过了,加&代表取这个数组成员的地址,如图一中蓝色位置的地址(注:不是蓝色位置的值),然后执行ngx_http_block(,,void *conf)函数,所以形参void *conf就是蓝色位置的地址。接下来看看传地址进去做什么。
先判断地址里面的值,即蓝色位置的值是否为NULL,如果为NULL,就生产一个ngx_http_conf_ctx_t 结构体指针,并赋值到这个蓝色位置。
typedef struct {
void **main_conf;
void **srv_conf;
void **loc_conf; }
ngx_http_conf_ctx_t;
蓝色位置指针指向的结构体的三个成员分别指向一个一维数组,分别指向各个模块的MAIN SRV LOC级别的结构体指针。
所以,conf代表核心模块ngx_http_module的配置项结构体的存储地址,这个地址存储的就是ngx_http_conf_ctx_t 类型的结构体指针。
顺便提一下 ,这里为什么要转为二级指针再解引用呢?
*(ngx_http_conf_ctx_t **) conf = ctx;
conf是地址,我们想在这个地址指向的内存赋值,那么肯定需要解引用。conf的原型是void *,如果对conf直接进行解引用,类型就是void了,显然不是指针类型。那么(ngx_http_conf_ctx_t *) conf = ctx可以吗?不行,这样的话conf是一个右值,不能被赋值,任何强转都属于右值,因为等号左边的表达式没有标明内存位置信息。好吧,那*(ngx_http_conf_ctx_t *) conf = ctx 总可以了吧?不好意思,还是不行,左边的表达式确实标明了内存从位置信息,就是conf指向的内存位置,但是这个位置的类型是ngx_http_conf_ctx_t 结构体,不符合我们的需求。
所以需要转成二级指针(ngx_http_conf_ctx_t **) conf,强转部分可以看成是声明,声明了conf为一个指针,它指向的类型是ngx_http_conf_ctx_t *,那么解引用后,*(ngx_http_conf_ctx_t **) conf表达式类型正好为ngx_http_conf_ctx_t *,标明一个内存位置,这个内存位置的类型是 ngx_http_conf_ctx_t *,正好符合需求。如果对指针的左右值有疑惑的,可以参考《C和指针》。
接下里就是解析http{}里面的内容了。
/* parse inside the http{} block */
cf->module_type = NGX_HTTP_MODULE;
cf->cmd_type = NGX_HTTP_MAIN_CONF;
rv = ngx_conf_parse(cf, NULL);
遇到server{}的时候会调用ngx_http_core_server(ngx_conf_t *cf, ngx_command_t *cmd, void *dummy) ,创建这个server所属的ngx_http_conf_ctx_t 结构体。
/* parse inside server{} */
pcf = *cf;
cf->ctx = ctx;
cf->cmd_type = NGX_HTTP_SRV_CONF;
rv = ngx_conf_parse(cf, NULL);
这里的cmd_type 不是
(三)nginx.conf LOC 配置项解析
上一节在运行ngx_http_core_server()执行到
/* parse inside server{} */
pcf = *cf;
cf->ctx = ctx;
cf->cmd_type = NGX_HTTP_SRV_CONF;
rv = ngx_conf_parse(cf, NULL);,会遇到location{ },这时候会调用
如上代码的时候会解析到location,会执行static char * ngx_http_core_location(ngx_conf_t *cf, ngx_command_t *cmd, void *dummy) ,这个函数也会创建当前location专有的 ngx_http_conf_ctx_t 结构体。