在《關于nginx事件模塊結構體的詳解》這篇文章中,我們講解nginx的事件模塊的整體工作流程,并且著重講解了組織事件模塊的各個方法的作用,本文則主要圍繞這整個流程,從源碼的角度講解nginx事件模塊的實現細節。
1. ngx_events_block()
—-events配置塊解析
nginx在解析nginx.conf
配置文件時,如果當前解析的配置項名稱為events
,并且是一個配置塊,則會調用ngx_events_block()
方法解析該配置塊,如下是該方法的源碼:
static char * ngx_events_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { char *rv; void ***ctx; ngx_uint_t i; ngx_conf_t pcf; ngx_event_module_t *m; // 如果存儲事件模塊配置數據的配置項不為空,說明已經解析過配置項了,因而直接返回 if (*(void **) conf) { return "is duplicate"; } // 這里主要是計算event模塊的個數,并且將各個event模塊的相對順序標記在了該模塊的ctx_index屬性中 ngx_event_max_module = ngx_count_modules(cf->cycle, NGX_EVENT_MODULE); // 創建一個存儲配置項數組的指針 ctx = ngx_pcalloc(cf->pool, sizeof(void *)); if (ctx == NULL) { return NGX_CONF_ERROR; } // 為配置項指針申請數組內存 *ctx = ngx_pcalloc(cf->pool, ngx_event_max_module * sizeof(void *)); if (*ctx == NULL) { return NGX_CONF_ERROR; } // 將數組值賦值到conf中,也即關聯到核心配置對象ngx_cycle_t中 *(void **) conf = ctx; for (i = 0; cf->cycle->modules[i]; i++) { if (cf->cycle->modules[i]->type != NGX_EVENT_MODULE) { continue; } m = cf->cycle->modules[i]->ctx; // 如果當前模塊的create_conf()方法不為空,則調用該方法創建存儲配置項的結構體 if (m->create_conf) { (*ctx)[cf->cycle->modules[i]->ctx_index] = m->create_conf(cf->cycle); if ((*ctx)[cf->cycle->modules[i]->ctx_index] == NULL) { return NGX_CONF_ERROR; } } } // 這里將*cf結構體進行了復制,臨時存儲在pcf中,然后初始化當前的*cf結構體的模塊相關的參數, // 以進行下一步的解析 pcf = *cf; cf->ctx = ctx; cf->module_type = NGX_EVENT_MODULE; cf->cmd_type = NGX_EVENT_CONF; // 解析events{}配置塊中的子配置項 rv = ngx_conf_parse(cf, NULL); // 重新將pcf復制給*cf,以供后面返回使用 *cf = pcf; if (rv != NGX_CONF_OK) { return rv; } // 到這里,說明events{}配置塊的配置項都解析完成了,因而這里調用各個模塊的init_conf()方法, // 進行配置項的初始化和合并工作 for (i = 0; cf->cycle->modules[i]; i++) { if (cf->cycle->modules[i]->type != NGX_EVENT_MODULE) { continue; } m = cf->cycle->modules[i]->ctx; // 如果當前模塊的init_conf()不為空,則調用其init_conf()方法初始化配置項 if (m->init_conf) { rv = m->init_conf(cf->cycle, (*ctx)[cf->cycle->modules[i]->ctx_index]); if (rv != NGX_CONF_OK) { return rv; } } } return NGX_CONF_OK; }
ngx_events_block()
方法主要完成的工作有如下幾個:
● 調用ngx_count_modules()
方法對事件模塊序號進行標記,需要注意的是,這里的排序是針對當前模塊在所有事件類型模塊中的順序進行標記,并且將序號保存在各模塊的ctx_index
屬性中,比如這里的事件類型核心模塊ngx_event_core_module
的ctx_index
就為0
;
● 為指針ctx
申請內存空間,并且申請一個數組,將其地址賦值給ctx指針,這里的數組長度就為事件模塊的數目。其實這里的數組就是用來保存每個事件模塊的配置對象的,當前事件模塊在所有事件模塊中的相對位置就對應于該數組中的相對位置,這里的相對位置也即前一步中計算得到的ctx_index
;
● 調用各個事件模塊的create_conf()
方法創建各自的配置結構體,并且將其保存在ctx
指針指向的數組中;
● 調用ngx_conf_parse()
方法對配置文件繼續解析,前面我們已經講到,ngx_events_block()
方法就是解析到events配置項的時候才調用的,因而這里的ngx_conf_parse()
方法的調用就是繼續解析events配置塊的子配置項,而該方法調用完成則說明events
配置塊里的配置項都已經解析完成;
● 調用各個模塊的init_conf()
方法對配置項進行初始化,簡單的說,就是,由于在nginx.conf
中只配置了部分配置項的值,而剩余的配置項就由init_conf()
方法來設置默認值;
2. ngx_event_init_conf()
—-檢查事件模塊配置結構體是否正常創建
在nginx解析完nginx.conf配置文件的所有配置項后(包括前一步中講解的對events配置項的解析),其就會調用所有核心模塊的init_conf()方法對核心模塊的配置項進行初始化。這里的核心模塊就包括ngx_events_module
,該模塊的init_conf()
方法指向的就是這里的ngx_event_init_conf()
方法,該方法本質上并沒有做什么工作,只是檢查了是否創建了存儲事件模塊配置項的結構體數組。
如下是ngx_event_init_conf()
方法的源碼:
static char *ngx_event_init_conf(ngx_cycle_t *cycle, void *conf) { if (ngx_get_conf(cycle->conf_ctx, ngx_events_module) == NULL) { ngx_log_error(NGX_LOG_EMERG, cycle->log, 0, "no "events" section in configuration"); return NGX_CONF_ERROR; } return NGX_CONF_OK; }
上面兩個方法就是ngx_events_module
核心模塊的兩個主要的配置方法,可以看到,這個核心模塊的主要作用就是創建了一個數組,用于存儲各個事件模塊的配置結構體的。下面我們來看一下事件核心模塊的主要方法。
3. ngx_event_core_create_conf()
—-創建事件核心模塊配置結構體
在第1點中我們講到,解析events配置塊的子配置項之前,會調用各個事件模塊的create_conf()
方法來創建其使用的存儲配置數據的結構體,而后調用ngx_conf_parse()
方法來解析子配置項,接著調用各個事件模塊的init_conf()方法初始化各個模塊配置數據的結構體。
這里ngx_event_core_module_ctx
就是一個事件類型的模塊,其create_conf屬性指向的就是ngx_event_core_create_conf()
方法,而init_conf
屬性指向的就是ngx_event_core_init_conf()
方法。
這一節我們首先講解ngx_event_core_create_conf()
方法的實現原理:
static void *ngx_event_core_create_conf(ngx_cycle_t *cycle) { ngx_event_conf_t *ecf; ecf = ngx_palloc(cycle->pool, sizeof(ngx_event_conf_t)); if (ecf == NULL) { return NULL; } ecf->connections = NGX_CONF_UNSET_UINT; ecf->use = NGX_CONF_UNSET_UINT; ecf->multi_accept = NGX_CONF_UNSET; ecf->accept_mutex = NGX_CONF_UNSET; ecf->accept_mutex_delay = NGX_CONF_UNSET_MSEC; ecf->name = (void *) NGX_CONF_UNSET; return ecf; }
可以看到,這里的ngx_event_core_create_conf()
方法本質上就是創建了一個ngx_event_conf_t
結構體,并且將各個屬性都設置為未設置狀態。
4. ngx_event_core_init_conf()
—-初始化配置結構體
前面我們講到,在解析完各個子配置項之后,nginx會調用各個事件模塊的init_conf()
方法,這里的核心事件模塊就是這個ngx_event_core_init_conf()
方法,如下是該方法的源碼:
static char * ngx_event_core_init_conf(ngx_cycle_t *cycle, void *conf) { ngx_event_conf_t *ecf = conf; #if (NGX_HAVE_EPOLL) && !(NGX_TEST_BUILD_EPOLL) int fd; #endif ngx_int_t i; ngx_module_t *module; ngx_event_module_t *event_module; module = NULL; #if (NGX_HAVE_EPOLL) && !(NGX_TEST_BUILD_EPOLL) // 測試是否具有創建epoll句柄的權限 fd = epoll_create(100); if (fd != -1) { // 關閉創建的epoll句柄,并且將module指向epoll模塊 (void) close(fd); module = &ngx_epoll_module; } else if (ngx_errno != NGX_ENOSYS) { module = &ngx_epoll_module; } #endif // 這里,如果沒有前面判斷的模塊類型,則默認使用事件模塊中的第一個模塊作為事件處理模型 if (module == NULL) { for (i = 0; cycle->modules[i]; i++) { if (cycle->modules[i]->type != NGX_EVENT_MODULE) { continue; } event_module = cycle->modules[i]->ctx; if (ngx_strcmp(event_module->name->data, event_core_name.data) == 0) { continue; } module = cycle->modules[i]; break; } } // 如果此時module還是為NULL,則返回異常 if (module == NULL) { ngx_log_error(NGX_LOG_EMERG, cycle->log, 0, "no events module found"); return NGX_CONF_ERROR; } // 下面的操作主要是判斷各個屬性是否為初始設置的無效值,如果是,則說明nginx.conf中沒有配置 // 關于該屬性的配置項,那么這里就會為該屬性設置默認值 ngx_conf_init_uint_value(ecf->connections, DEFAULT_CONNECTIONS); cycle->connection_n = ecf->connections; ngx_conf_init_uint_value(ecf->use, module->ctx_index); event_module = module->ctx; ngx_conf_init_ptr_value(ecf->name, event_module->name->data); ngx_conf_init_value(ecf->multi_accept, 0); ngx_conf_init_value(ecf->accept_mutex, 0); ngx_conf_init_msec_value(ecf->accept_mutex_delay, 500); return NGX_CONF_OK; }
ngx_event_core_init_conf()方法的主要做了兩件事:
● 選擇當前所使用的模塊,如果沒指定,則默認使用第一個事件模塊;
● 初始化事件核心模塊的配置結構體的各個屬性值為默認值。
5. ngx_event_module_init()
—-核心模塊的配置項初始化
對于ngx_event_core_module
模塊而言,其還指定了兩個方法,一個是用于初始化模塊的ngx_event_module_init
()
方法,另一個是用于worker進程執行主循環邏輯之前進行調用的ngx_event_process_init()
方法。
ngx_event_module_init()
方法是在master進程中調用的,其會在解析完nginx.conf文件中的所有配置項之后調用,本質上,該方法的作用就是對當前配置的核心模塊(事件模塊)進行初始化。
如下是ngx_event_module_init()
方法的源碼:
/** * 當前方法的主要作用是申請一塊用于存儲統計數據的共享內存,然后設置ngx_accept_mutex_ptr、 * ngx_connection_counter、ngx_temp_number等變量的地址,如果開啟了slab stat, * 那么還會設置ngx_stat_accepted、ngx_stat_handled、ngx_stat_requests等的地址,以統計