http://blog.csdn.net/chosen0ne/article/details/7728294
1. 重要的数据结构
1. ngx_module_t
nginx中所有模块的类型都是ngx_module_t类型的,定义了模块的一些属性。nginx是完全模块化的,所有的组件都是模块,从而实现了nginx的高度松耦合。同时,我们在进行nginx模块开发时,也离不开这个数据结构。
- struct ngx_module_s {
-
-
-
-
-
-
-
- ngx_uint_t ctx_index;
-
-
-
-
-
-
-
- ngx_uint_t index;
-
- ……
-
-
-
-
-
-
-
- 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);
- ……
- };
下面的表格是type与ctx的对应关系:
模块类型(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) |
2. ngx_commond_t
ngx_commond_t描述的是模块的配置指令,也就是出现在配置文件的指令。nginx模块支持多个配置指令,所以是以ngx_commond_t数组形式存储的。这个结构在配置文件解析和模块的配置结构信息初始化时会用到。
- 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;
- };
3. ngx_cycle_t
ngx_cycle_t是nginx中最重要的数据结构,包含了全局的配置信息、所有监听的套接字、连接池、读写事件等。ngx_cycle_t相当于nginx的一个生命周期,从nginx启动后直到向nginx发送stop或者reload信号。nginx中有一个全局变量ngx_cycle指向当前的cycle。
- struct ngx_cycle_s {
-
-
-
-
-
- void ****conf_ctx;
-
-
-
-
-
- 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_array_t listening;
- ngx_array_t pathes;
-
-
-
-
-
-
-
- ngx_list_t open_files;
-
-
-
-
-
-
- ngx_list_t shared_memory;
-
- ngx_uint_t connection_n;
- ngx_uint_t files_n;
-
-
-
-
- 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;
- ngx_str_t conf_prefix;
- ngx_str_t prefix;
- ngx_str_t lock_file;
- ngx_str_t hostname;
- };
2. nginx启动过程
nginx启动显然是由main函数驱动的,在core/nginx.c文件中,整个启动过程主要是初始化ngx_cycle_t类型的全局变量ngx_cycle。下面分析按照nginx的主要执行流程讲述的,首先从main函数开始。
(1) main函数
- 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();
-
- (NGX_PCRE)
- ngx_regex_init();
- if
-
- ngx_pid = ngx_getpid();
-
-
- log = ngx_log_init(ngx_prefix);
- if (log == NULL) {
- return 1;
- }
上面几行代码主要完成时间、正则表达式和log的初始化。
-
-
-
-
-
- ngx_memzero(&init_cycle, sizeof(ngx_cycle_t));
- init_cycle.log = log;
- ngx_cycle = &init_cycle;
-
-
- 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多行,这里只讲解大概的流程,细节不在展开。
(2) ngx_init_cycle函数
在ngx_cycle_t结构中包含各种ngx_list_t、ngx_array_t等类型的属性,ngx_init_cycle的前面200多行代码都是对这些数据结构进行初始化。主要有
1. 更新时区和时间。
2. 创建内存池。
3. 根据old_cycle初始化cycle中的conf_file、conf_prefix、prefix和conf_param。
4. 初始化pathes(ngx_array_t)。
5. 初始化openfiles(ngx_list_t)。
6. 初始化shared_memory(ngx_list_t)。
7. 初始化listening(ngx_array_t)。
8. 初始化conf_ctx(void ****)数组,大小是ngx_max_module,用于存储所有core module的配置结构信息。
9. 调用系统调用gethostname获取主机名,初始化hostname。
介绍完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->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_core_module(core/nginx.c)
ngx_http_module(http/ngx_http.c)
ngx_events_module(event/ngx_event.c)
ngx_errlog_module(core/ngx_log.c)
ngx_mail_module(mail/ngx_mail.c)
ngx_openssl_module(event/ngx_event_openssl.c)
ngx_google_perftools_module(misc/ngx_google_perftools_module.c)
只有ngx_core_module和ngx_google_perftools_module两个模块有定义create_conf,而ngx_google_perftools_module仅用于性能测试,所以真正使用时只有ngx_core_module有create_conf回调函数。这个会调用函数会创建ngx_core_conf_t结构,用于存储整个配置文件main scope范围内的信息,比如worker_processes,worker_cpu_affinity等。
- ngx_memzero(&conf, sizeof(ngx_conf_t));
-
- conf.args = ngx_array_create(pool, 10, sizeof(ngx_str_t));
- 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;
- }
-
-
- 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_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) {
-
- exit(1);
- }
- }
- }
调用所有模块的init_module回调函数,进行模块的初始化动作。ngx_init_cycle最后部分代码主要就是释放多余的资源,包括关闭共享内存、监听socket已经打开的文件等,然后ngx_init_cycle正常返回,接下来继续看main函数。
(3) 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模块的启动流程,这样大家就可以了解一个具体模块是怎么初始化的的。