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模块的启动流程,这样大家就可以了解一个具体模块是怎么初始化的的。