struct ngx_module_s {
/**
* 在具体类型模块(http、event等)的全局配置结构数组的下标。以http module模块为例,
* nginx把所有的http module的config信息存放在ngx_http_conf_ctx_t类型的变量中,
* 这个变量只有3个属性,分别是所有http module的main、srv、loc的config信息的数组。
* 如果该模块是http module,则ctx_index是该模块的config信息(main、srv、loc)
* 在ngx_http_conf_ctx_t中的下标。
*/
ngx_uint_t ctx_index;
/**
* nginx把所有模块(ngx_module_t)存放到ngx_modules数组中,这个数组在nginx源码路
* 径的objs/ngx_modules.c中,是在运行configure脚本后生成的。index属性就是该模块
* 在ngx_modules数组中的下标。同时nginx把所有的core module的配置结构存放到ngx_cycle的
* conf_ctx数组中,index也是该模块的配置结构在ngx_cycle->conf_ctx数组中的下标。
*/
ngx_uint_t index;
……
/**
* 模块的上下文属性,同一类型的模块的属性是相同的,比如core module的ctx是ngx_core_module_t类型。
* 而http module的ctx是ngx_http_moduel_t类型,event module的ctx是ngx_event_module_t类型等等。
* 相应类型的模块由分开处理的,比如所有的http module由ngx_http_module解析处理,而所有的event module
* 由ngx_events_module解析处理。
*/
void *ctx;
/**
* 该模块支持的指令的数组,最后以一个空指令结尾。ngx_commond_t的分析见下文。
*/
ngx_command_t *commands;
/**
* 模块的类型,nginx所有的模块类型:
* NGX_CORE_MODULE
* NGX_CONF_MODULE
* NGX_HTTP_MODULE
* NGX_EVENT_MODULE
* NGX_MAIL_MODULE
* 这些不同的类型也指定了不同的ctx。
*/
ngx_uint_t type;
/* 接下来都是一些回调函数,在nginx初始化过程的特定时间点调用 */
ngx_int_t (*init_master)(ngx_log_t *log);
/* 初始化完所有模块后调用,在ngx_int_cycle函数(ngx_cycle.c)中 */
ngx_int_t (*init_module)(ngx_cycle_t *cycle);
/* 初始化完worker进程后调用,在ngx_worker_process_init函数(ngx_process_cycle.c)中 */
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);
……
};
模块类型(type) | 上下文属性类型(ctx) |
NGX_CORE_MODULE
|
ngx_core_module_t(ngx_conf_file.h)
|
NGX_CONF_MODULE
|
NULL |
NGX_HTTP_MODULE
|
ngx_http_module_t(http/ngx_http_config.h)
|
NGX_EVENT_MODULE
|
ngx_event_module_t(event/ngx_event.h)
|
NGX_MAIL_MODULE
|
ngx_mail_module_t(mail/ngx_mail.h) |
struct ngx_command_s {
/**
* 指令名,与配置文件中一致
*/
ngx_str_t name;
/**
* 指令的类型,以及参数的个数。这个属性有两个作用:
* 1. 实现只解析某个类型的指令,比如当前这个指令是event module类型的,而正在解析的是
* http module,所以会跳过所有不是http module类型的指令。
* 2. 实现指令参数个数的校验。
*/
ngx_uint_t type;
/*
* 回调函数,在解析配置文件时,遇到这个指令时调用。
* cf: 包括配置参数信息cf->args(ngx_array_t类型),以及指令对应的模块上下文cf->ctx
* 在解析不同模块的指令时,这个上下文信息不同。比如在解析core module时,cf->ctx
* 是ngx_cycle->conf_ctx也就是所有core module的配置结构数组,而在解析http module
* 时cf->ctx是ngx_http_conf_ctx_t类型的,其中包含所有http module的main、srv、loc
* 的配置结构数组。
* cmd: 指令对应的ngx_command_t结构。
* conf:指令对应的模块的配置信息。
*/
char *(*set)(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
/**
* 对http module有效,http module的配置结构信息(main、srv、loc)都存放在ngx_http_conf_ctx_t
* 中对应的数组,conf属性指示这个指令的配置结构是main、srv还是loc。
*/
ngx_uint_t conf;
/**
* 指令对应属性在模块配置结构中的偏移量。
*/
ngx_uint_t offset;
/**
* 一般是函数指针,在set回调函数中调用。
*/
void *post;
};
struct ngx_cycle_s {
/**
* 所有core module的配置结构信息的数组,大小是ngx_max_module。配置结构信息由
* ngx_core_module_t的create_config回调函数生成,并有init_config回调函数初始化。
* module的index属性指示了该模块的配置结构信息在conf_ctx数组中的下标。
*/
void ****conf_ctx;
/**
* 随ngx_cycle_t同生命周期的内存池,是生命周期最长的,除非nginx关闭或重启。内存块大小
* 由NGX_CYCLE_POOL_SIZE定义,是16KB。
*/
ngx_pool_t *pool;
ngx_log_t *log; // 日志
ngx_log_t new_log;
ngx_connection_t **files; // 链接文件
ngx_connection_t *free_connections; // 空闲链接数组
ngx_uint_t free_connection_n; // 空闲链接个数
ngx_queue_t reusable_connections_queue; // 重用链接队列
/**
* 监听套接字的数组,类型是ngx_listening_t,ngx_create_listening函数会向这个数组
* 添加新的监听句柄。
*/
ngx_array_t listening;
ngx_array_t pathes; // 路径数组,ngx_path_t
/**
* 所有打开的文件描述符的链表,ngx_open_file_t类型。ngx_conf_file.c中的
* ngx_conf_open_file(ngx_cycyle_t *cycle, ngx_str_t *name)
* 可以返回名字为name的打开的文件描述符,如果对应的描述符不存在,则打开并存入open_files中。
* 比如:logs/error.log的文件描述符就在其中。
*/
ngx_list_t open_files;
/**
* 共享内存的链表,ngx_shm_zone_t类型。nginx会把需要进程共享的数据放在共享内存中。
* 比如,accept锁就放在这里。worker进程只有获取这个锁之后,才能accept到新的连接。
* 防止惊群。
*/
ngx_list_t shared_memory;
ngx_uint_t connection_n; // 链接个数
ngx_uint_t files_n; // 打开文件个数
/**
* 连接池,nginx把连接池组织成综合数组和链表特性的一种数据结构,后文会详细介绍。
*/
ngx_connection_t *connections;
ngx_event_t *read_events; // 读事件
ngx_event_t *write_events; // 写事件
ngx_cycle_t *old_cycle;
ngx_str_t conf_file; // 配置文件名
ngx_str_t conf_param; // 由命令行-g提供配置参数
ngx_str_t conf_prefix; // 配置前缀
ngx_str_t prefix; // nginx所在路径
ngx_str_t lock_file;
ngx_str_t hostname; // 主机名
};
if (ngx_strerror_init() != NGX_OK) {
return 1;
}
初始化错误提示列表,以errno为下标,元素就是对应的错误提示信息。
if (ngx_get_options(argc, argv) != NGX_OK) {
return 1;
}
获取命令行参数,比如-t、-c、g等。这里没有使用linux的getopt,主要是为了跨平台。
ngx_time_init();
#if (NGX_PCRE)
ngx_regex_init();
#endif
ngx_pid = ngx_getpid();
// 初始化log
log = ngx_log_init(ngx_prefix);
if (log == NULL) {
return 1;
}
上面几行代码主要完成时间、正则表达式和log的初始化。
/*
* init_cycle->log is required for signal handlers and
* ngx_process_options()
*/
ngx_memzero(&init_cycle, sizeof(ngx_cycle_t));
init_cycle.log = log;
ngx_cycle = &init_cycle;
// cycle的内存池以1024大小的内存块
init_cycle.pool = ngx_create_pool(1024, log);
if (init_cycle.pool == NULL) {
return 1;
}
if (ngx_save_argv(&init_cycle, argc, argv) != NGX_OK) {
return 1;
}
初始化cycle结构,并创建内存块大小为1024的内存池,ngx_save_argv会将命令行参数保存到ngx_os_argv,ngx_argc和ngx_argv,以便于之后master进程完成热代码替换。
if (ngx_process_options(&init_cycle) != NGX_OK) {
return 1;
}
处理配置文件相关信息,初始化cycle的conf_prefix(配置文件所在路径的前缀)、prefix(nginx可执行文件所在路径)、conf_file(配置文件名)和conf_param(通过命令行-g选项指定的全局配置信息)。
if (ngx_os_init(log) != NGX_OK) {
return 1;
}
处理操作系统的相关信息,比如页大小ngx_pagesize等。
if (ngx_crc32_table_init() != NGX_OK) {
return 1;
}
初始化循环冗余检验表。
if (ngx_add_inherited_sockets(&init_cycle) != NGX_OK) {
return 1;
}
通过环境变量NGINX完成socket的继承,这些继承来的socket会放到init_cycyle的listening数组中。
ngx_max_module = 0;
for (i = 0; ngx_modules[i]; i++) {
ngx_modules[i]->index = ngx_max_module++;
}
这段代码是对所有nginx的模块计数,同时更新模块的index属性。ngx_max_module就是所有模块的总数。关于index的解释见上文对ngx_module_t的解释。
cycle = ngx_init_cycle(&init_cycle);
这行代码是nginx启动过程中最重要的一个步骤,用于初始化ngx_cycle。下面就进入ngx_init_cycle看看具体cycle的初始化过程。ngx_init_cycle代码有800多行,这里只讲解大概的流程,细节不在展开。
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;
}
}
这段代码的逻辑是调用所有core module的create_conf回调函数创建该core module的配置信息结构,并且更新cycle->conf_ctx数组。nginx的core module主要有:
ngx_memzero(&conf, sizeof(ngx_conf_t));
/* STUB: init array ? */
conf.args = ngx_array_create(pool, 10, sizeof(ngx_str_t)); // args是指令后带的参数的数组
if (conf.args == NULL) {
ngx_destroy_pool(pool);
return NULL;
}
conf.temp_pool = ngx_create_pool(NGX_CYCLE_POOL_SIZE, log);
if (conf.temp_pool == NULL) {
ngx_destroy_pool(pool);
return NULL;
}
/* 解析core module对应的指令,此时conf的上下文是cycle->conf_ctx,就是所有core module的config信息 */
conf.ctx = cycle->conf_ctx;
conf.cycle = cycle;
conf.pool = pool;
conf.log = log;
conf.module_type = NGX_CORE_MODULE; // conf.module_type指示将要解析这个类型模块的指令。
conf.cmd_type = NGX_MAIN_CONF; // conf.cmd_type指示将要解析的指令的类型。
这段代码是为解析配置文件做准备。初始化ngx_conf_t,用于解析配置文件并保存解析出来的信息。args是配置文件中指令的信息的数组,args[0]是指令名,args[1] - args[n]是指令的参数,参数个数需要根据ngx_commond_t的type属性做校验。ngx_conf_t中的module_type和cmd_type用于控制解析什么类型的指令,module_type表示只解析该类型模块包含的指令,cmd_type表示将要解析的指令的类型,也就是说只有符合module_type和cmd_type的指令才会被解析。比如module_type取NGX_HTTP_MODULE,而cmd_type取NGX_HTTP_SRV_CONF,那么在一次配置文件解析中,只会对http module的server块的指令进行解析。
if (ngx_conf_param(&conf) != NGX_CONF_OK) {
environ = senv;
ngx_destroy_cycle_pools(&conf);
return NULL;
}
对通过nginx -g xxx 设置的全局配置指令初始化、解析。
if (ngx_conf_parse(&conf, &cycle->conf_file) != NGX_CONF_OK) {
environ = senv;
ngx_destroy_cycle_pools(&conf);
return NULL;
}
解析配置文件。配置文件的解析类似一棵树的遍历,nginx中的指令分为块指令和普通指令,每个块指令对应一棵子树,比如http块和event块。由这些块指令负责调用ngx_conf_parse函数解析块内部的指令。配置文件的具体分析会另开一片文章,这里忽略这些细节。在ngx_conf_parse函数返回后,整个配置文件解析完毕,所有模块的指令已经初始化,也就意味着所有模块基本上都初始化完,实际上ngx_conf_parse函数后面隐藏了大量的信息,包括http模块的初始化和事件模块的初始化。关于http的初始化我们后面再详细描述,这里接着讲述ngx_init_cycle。
for (i = 0; ngx_modules[i]; i++) {
if (ngx_modules[i]->type != NGX_CORE_MODULE) {
continue;
}
module = ngx_modules[i]->ctx;
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;
}
}
}
初始化所有core module模块的config结构调用ngx_core_module_t的init_conf 。在所有core module中,只有ngx_core_module有init_conf回调,用于对ngx_core_conf_t中没有配置的字段设置默认值。
if (ngx_test_config) {
if (ngx_create_pidfile(&ccf->pid, log) != NGX_OK) {
goto failed;
}
} else if (!ngx_is_init_cycle(old_cycle)) {
old_ccf = (ngx_core_conf_t *) ngx_get_conf(old_cycle->conf_ctx,
ngx_core_module);
if (ccf->pid.len != old_ccf->pid.len
|| ngx_strcmp(ccf->pid.data, old_ccf->pid.data) != 0)
{
if (ngx_create_pidfile(&ccf->pid, log) != NGX_OK) {
goto failed;
}
ngx_delete_pidfile(old_cycle);
}
}
这段代码很简单就是创建nginx的pid文件。随后的很长一段代码用于创建所有的文件路径、打开文件描述符以及创建共享内存,这里就不贴代码了。接下来的一段代码是用于处理监听socket的,如果监听地址相同的话,则把新、旧cycle的监听socket合并。
if (ngx_open_listening_sockets(cycle) != NGX_OK) {
goto failed;
}
ngx_open_listening_sockets函数的功能就同它名字一样,用于打开所有的监听socket,具体过程和用socket编程时是一样的,调用socket创建套接字 -> 调用setsockopt设置成可重用socket -> 设置成非阻塞socket -> 调用bind绑定要监听的socket地址 -> 调用listen转化成监听socket。
if (!ngx_test_config) {
ngx_configure_listening_sockets(cycle);
}
根据cycle配置所有的监听socket,包括设置监听socket的接收缓冲区大小、发送缓冲区大小以及accept filter等。
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);
}
}
}
调用所有模块的init_module回调函数,进行模块的初始化动作。ngx_init_cycle最后部分代码主要就是释放多余的资源,包括关闭共享内存、监听socket已经打开的文件等,然后ngx_init_cycle正常返回,接下来继续看main函数。
if (ngx_init_signals(cycle->log) != NGX_OK) {
return 1;
}
注册信号,这些信号是定义在os/unix/ngx_process.c中的signals数组,元素的类型是ngx_signal_t,这个结构定义了信号的值、名字已经信号处理函数。
if (!ngx_inherited && ccf->daemon) {
if (ngx_daemon(cycle->log) != NGX_OK) {
return 1;
}
ngx_daemonized = 1;
}
这段代码很明显是实现守护进程的。
if (ngx_create_pidfile(&ccf->pid, cycle->log) != NGX_OK) {
return 1;
}
接下来是创建nginx的pid文件。main函数的最后是关于进程模型的初始化的。
if (ngx_process == NGX_PROCESS_SINGLE) {
ngx_single_process_cycle(cycle);
} else {
ngx_master_process_cycle(cycle);
}
可以看出,这里分为单进程和多进程模式,大部分情况我们都采用多进程模式。这部分内容会在另一篇专门讲述nginx进程模型的文章中再详细介绍。介绍完整个nginx的启动过程,可以总结出启动过程就是一堆的初始化工作,其中最重要的是配置文件解析的过程,在一个ngx_conf_parse函数背后隐藏了大量的秘密,下一篇源码分析就会介绍http模块的启动流程,这样大家就可以了解一个具体模块是怎么初始化的的。