当前位置: > Linux服务器 > nginx >

nginx的启动流程分析(一)

时间:2015-10-13 11:07来源:linux.it.net.cn 作者:IT

这篇我们会主要来分析配置文件相关的一些初始化,而在下一篇我们会详细分析http协议相关,以及socket的初始化信息。

nginx启动最重要的部分是在ngx_init_cycle中,我们接下来就会详细的分析这个函数,以及相关的函数.

下面就是ngx_init_cycle的流程图

首先先来看几个相关的数据结构。 在nginx中,模块的结构是这样子的,首先所有的模块都是用ngx_module_t来表示,而模块又分为三类,分别是ngx_core_module_t和ngx_http_module_t,而在ngx_module_t中会包含这两个结构,只不过不同类的模块包含不同的结构。一般来说这部分就叫做ctx,我们写模块都会先定义一个ctx,然后包含到ngx_module_t中。这里有个type域用来标识模块的类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
struct ngx_module_s {
void  ****conf_ctx;
//ctx索引
    ngx_uint_t            ctx_index;
    ngx_uint_t            index;
........................................................
    ngx_uint_t            version;
//ctx
    void                 *ctx;
    ngx_command_t        *commands;
    ngx_uint_t            type;
 
    ngx_int_t           (*init_master)(ngx_log_t *log);
 
    ngx_int_t           (*init_module)(ngx_cycle_t *cycle);
 
    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);
 
    void                (*exit_master)(ngx_cycle_t *cycle);
.............................................................
};

这里看到有两个index,分别是ctx_index和index,他们的区别是这样子的,ctx_index保存了每一个http module的config的索引,而所有的http module config是分别保存在nginx_conf_t的ctx数组中的.而index保存了每一个core module的config,而每个core module的config都是保存在cycle的conf_ctx中的,下面的代码能够很明显看出他们的不同。

1
2
3
4
#define ngx_http_conf_get_module_main_conf(cf, module)                        \
    ((ngx_http_conf_ctx_t *) cf->ctx)->main_conf[module.ctx_index]
 
#define ngx_get_conf(conf_ctx, module)  conf_ctx[module.index]

ngx_core_module_t都包括(log, event, event_openssl, http, mail,google perftools),可以看到http module本身也是一个core module。这里要注意还有一个conf module,只不过它也是用core module这个数据结构。

1
2
3
4
5
typedef struct {
    ngx_str_t             name;
    void               *(*create_conf)(ngx_cycle_t *cycle);
    char               *(*init_conf)(ngx_cycle_t *cycle, void *conf);
} ngx_core_module_t;

ngx_http_module_t包括所有src/http/下面的模块,它就包含了所有的http module,它们都从属于http core模块。

1
2
3
4
5
6
7
8
9
10
11
12
13
typedef struct {
    ngx_int_t   (*preconfiguration)(ngx_conf_t *cf);
    ngx_int_t   (*postconfiguration)(ngx_conf_t *cf);
 
    void       *(*create_main_conf)(ngx_conf_t *cf);
    char       *(*init_main_conf)(ngx_conf_t *cf, void *conf);
 
    void       *(*create_srv_conf)(ngx_conf_t *cf);
    char       *(*merge_srv_conf)(ngx_conf_t *cf, void *prev, void *conf);
 
    void       *(*create_loc_conf)(ngx_conf_t *cf);
    char       *(*merge_loc_conf)(ngx_conf_t *cf, void *prev, void *conf);
} ngx_http_module_t;

然后我们看到在ngx_module_t中还有一个很重要的域,那就是ngx_command_t,这个域对应了当前的模块所包含的所有指令,这个域主要是供nginx解析配置文件时使用,设置相关的数据结构。

1
2
3
4
5
6
7
8
9
struct ngx_command_s {
    ngx_str_t             name;
    ngx_uint_t            type;
//指令对应的回调函数。
    char               *(*set)(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
    ngx_uint_t            conf;
    ngx_uint_t            offset;
    void                 *post;
};

上面只是简单的介绍几个数据结构,接下来配合代码,我们会看到这些结构中的回调函数,域都是如何被调用,以及调用顺序是如何的。

来看ngx_init_cycle,这个函数比较长,我们只分析我们关心的部分。这里要注意,下面的代码是完全按照顺序进行分析的,因为这里我们非常关注这些回调函数什么的顺序。

下面这段代码片段主要是创建所有core module的configure.它通过调用每个core module的create_conf方法,来创建对应的conf,然后将这个conf对象保存在全局的conf_ctx中,这样后面如果想取得这个config对象,则之需要通过简单的索引就能得到。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
    for (i = 0; ngx_modules[i]; i++) {
        if (ngx_modules[i]->type != NGX_CORE_MODULE) {
            continue;
        }
//得到core module
        module = ngx_modules[i]->ctx;
//如果create_conf存在,则直接创建config.
        if (module->create_conf) {
            rv = module->create_conf(cycle);
            if (rv == NULL) {
                ngx_destroy_pool(pool);
                return NULL;
            }
//保存config.
            cycle->conf_ctx[ngx_modules[i]->index] = rv;
        }
    }

当所有的core module的config都创建完毕后,就要开始解析配置文件了,解析配置文件它会一行行读取,然后如果遇到指令,则会查找到对应的ngx_command_t对象,然后执行对应的回调set方法。这里所有动作都在ngx_conf_parse这个函数中进行.
这里要注意一个东西,那就是commands是分两种类型的,一种是一般的命令,这里之需要直接调用set进行设置,而另外一种就是命令本身包括大括号的,比如types, geo,http 这些,这些命令的话,nginx这里是通过在命令本身的set函数里面设置conf的hand来做的,我们来看下types的set回调ngx_http_core_types。

他的代码很简单,就是设置对应的handler,保存当前的cf,然后调用ngx_conf_parse继续解析下面的,最后解析完毕(也就是当前的命令结束),恢复conf。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
static char *
ngx_http_core_types(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
    ngx_http_core_loc_conf_t *clcf = conf;
 
    char        *rv;
    ngx_conf_t   save;
...............................
//保存conf
    save = *cf;
//设置handler
    cf->handler = ngx_http_core_type;
    cf->handler_conf = conf;
//继续解析
    rv = ngx_conf_parse(cf, NULL);
//恢复conf
    *cf = save;
    return rv;
}

然后在ngx_conf_parse会判断cf是否有handler回调,如果有的话,优先调用handler回调,如果没有,则会进入ngx_conf_handler进行一般处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//如果handler存在,则调用handler
        if (cf->handler) {
 
            /*
             * the custom handler, i.e., that is used in the http's
             * "types { ... }" directive
             */
 
            rv = (*cf->handler)(cf, NULL, cf->handler_conf);
            if (rv == NGX_CONF_OK) {
                continue;
            }
            goto failed;
        }
//否则进入一般的处理,
        rc = ngx_conf_handler(cf, rc);

下面就是ngx_conf_handler的片段,代码很简单,就是遍历模块的command,比较名字,然后调用回调函数set。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
    for (i = 0; ngx_modules[i]; i++) {
 
        /* look up the directive in the appropriate modules */
 
        if (ngx_modules[i]->type != NGX_CONF_MODULE
            && ngx_modules[i]->type != cf->module_type)
        {
            continue;
        }
 
        cmd = ngx_modules[i]->commands;
        if (cmd == NULL) {
            continue;
        }
 
        for ( /* void */ ; cmd->name.len; cmd++) {
................................................
//调用set。
            rv = cmd->set(cf, cmd, conf);
 
            if (rv == NGX_CONF_OK) {
                return NGX_OK;
            }

ok,接下来或许有个疑问,那就是前面只是创建了core module的config,然后解析配置文件的时候会保存http module的config的一些东西,那么http module相关的config在那里创建呢?

http module相关的config是在ngx_http_block中创建的,在ngx_http_block中会创建,初始化,合并config,以及整个http handler phase的初始化等等。

首先是初始化所有的http module的ctx_index.

1
2
3
4
5
6
7
8
    ngx_http_max_module = 0;
    for (m = 0; ngx_modules[m]; m++) {
        if (ngx_modules[m]->type != NGX_HTTP_MODULE) {
            continue;
        }
//每个模块都有自己对应的索引值.
        ngx_modules[m]->ctx_index = ngx_http_max_module++;
    }

然后就是创建http module的对应的main,srv,loc config,这里很简单就是调用对应的create_xxx_conf回调函数。这里可以看到所有的http module相关的config都是保存在ngx_http_conf_ctx_t中。ngx_http_conf_ctx_t这个结构很简单,就是保存了三个数组,分别是main,srv,loc 的conf,其中每个都保存了所有的http module的对应的conf。

1
2
3
4
5
typedef struct {
    void        **main_conf;
    void        **srv_conf;
    void        **loc_conf;
} ngx_http_conf_ctx_t;

接下来来看ngx_http_block剩下的代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
    ngx_http_conf_ctx_t         *ctx;
//开始初始化,可以看到默认会分配max个config
    ctx->main_conf = ngx_pcalloc(cf->pool,
                                 sizeof(void *) * ngx_http_max_module);
    if (ctx->main_conf == NULL) {
        return NGX_CONF_ERROR;
    }
//下面省略了srv和loc的创建
.......................................
 
//开始遍历
    for (m = 0; ngx_modules[m]; m++) {
        if (ngx_modules[m]->type != NGX_HTTP_MODULE) {
            continue;
        }
//得到对应的module上下文
        module = ngx_modules[m]->ctx;
//得到对应的索引
        mi = ngx_modules[m]->ctx_index;
//如果有对应的回调,则调用回调函数,然后将返回的模块config设置到ctx的对应的conf列表中。
        if (module->create_main_conf) {
            ctx->main_conf[mi] = module->create_main_conf(cf);
            if (ctx->main_conf[mi] == NULL) {
                return NGX_CONF_ERROR;
            }
        }
        if (module->create_srv_conf) {
            ctx->srv_conf[mi] = module->create_srv_conf(cf);
            if (ctx->srv_conf[mi] == NULL) {
                return NGX_CONF_ERROR;
            }
        }
//下面省略了loc的调用。
    }

而当创建完毕之后,真正初始化模块之前需要调用preconfiguration来进行一些操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
    cf->ctx = ctx;
 
    for (m = 0; ngx_modules[m]; m++) {
        if (ngx_modules[m]->type != NGX_HTTP_MODULE) {
            continue;
        }
 
        module = ngx_modules[m]->ctx;
//调用preconfiguration。
        if (module->preconfiguration) {
            if (module->preconfiguration(cf) != NGX_OK) {
                return NGX_CONF_ERROR;
            }
        }
    }

然后就是继续parse config.

1
2
3
cf->module_type = NGX_HTTP_MODULE;
cf->cmd_type = NGX_HTTP_MAIN_CONF;
rv = ngx_conf_parse(cf, NULL);

当http block完全parse完毕之后,就需要merge(main和srv或者srv和loc)相关的config了。不过在每次merge之前都会首先初始化main conf。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
    for (m = 0; ngx_modules[m]; m++) {
        if (ngx_modules[m]->type != NGX_HTTP_MODULE) {
            continue;
        }
//和上面类似,首先取得模块以及对应索引。
        module = ngx_modules[m]->ctx;
        mi = ngx_modules[m]->ctx_index;
 
        /* init http{} main_conf's */
//如果有init_main_conf,则首先初始化main conf.
        if (module->init_main_conf) {
            rv = module->init_main_conf(cf, ctx->main_conf[mi]);
            if (rv != NGX_CONF_OK) {
                goto failed;
            }
        }
//然后开始merge config。
        rv = ngx_http_merge_servers(cf, cmcf, module, mi);
        if (rv != NGX_CONF_OK) {
            goto failed;
        }
    }

所有的merge动作都在ngx_http_merge_servers中,这个函数这里就不分析了,他主要就是遍历所有的server,然后判断模块是否有merge回调函数,如果有的话,就调用回调函数。

这里对location的处理部分就不进行分析了,这里是一个很复杂的地方,以后会专门写一篇blog来分析这部分。

当merge完毕之后,然后就是初始化location tree,创建handler phase,调用postconfiguration,以及变量的初始化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//初始化handler phase
    if (ngx_http_init_phases(cf, cmcf) != NGX_OK) {
        return NGX_CONF_ERROR;
    }
//遍历模块,然后调用对应的postconfiguration.
    for (m = 0; ngx_modules[m]; m++) {
        if (ngx_modules[m]->type != NGX_HTTP_MODULE) {
            continue;
        }
 
        module = ngx_modules[m]->ctx;
//调用回调
        if (module->postconfiguration) {
            if (module->postconfiguration(cf) != NGX_OK) {
                return NGX_CONF_ERROR;
            }
        }
    }
//开始初始化变量
    if (ngx_http_variables_init_vars(cf) != NGX_OK) {
        return NGX_CONF_ERROR;
    }

当这些都做完之后,就开始初始化socket相关的东西,比如设置读写回调函数等等,这个会在下一篇详细分析。

1
2
3
4
/* optimize the lists of ports, addresses and server names */
if (ngx_http_optimize_servers(cf, cmcf, cmcf->ports) != NGX_OK) {
    return NGX_CONF_ERROR;
}

再接着看ngx_cycle_init剩下的部分,当配置文件解析完毕后,就开始初始化core module的config

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
    for (i = 0; ngx_modules[i]; i++) {
        if (ngx_modules[i]->type != NGX_CORE_MODULE) {
            continue;
        }
 
        module = ngx_modules[i]->ctx;
//调用init_conf
        if (module->init_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;
            }
        }
    }

再紧接着就是初始化所有创建的共享内存。

1
2
3
4
5
6
7
8
9
10
11
if (ngx_shm_alloc(&shm_zone[i].shm) != NGX_OK) {
    goto failed;
}
 
if (ngx_init_zone_pool(cycle, &shm_zone[i]) != NGX_OK) {
    goto failed;
}
 
if (shm_zone[i].init(&shm_zone[i], NULL) != NGX_OK) {
    goto failed;
}

然后是listen socket的初始化,这里还记得前面的http_block中也有socket的初始化,这里要注意,那边只是挂载对应的hook,这里才是创建并bind等操作。

1
2
3
if (ngx_open_listening_sockets(cycle) != NGX_OK) {
    goto failed;
}

等这些都做完则是调用init_module对所有的模块进行初始化。

1
2
3
4
5
6
7
8
for (i = 0; ngx_modules[i]; i++) {
    if (ngx_modules[i]->init_module) {
        if (ngx_modules[i]->init_module(cycle) != NGX_OK) {
            /* fatal */
            exit(1);
        }
    }
}

然后ngx_init_cycle剩下就是一些清理工作了。

(责任编辑:IT)
------分隔线----------------------------
栏目列表
推荐内容