nginx原始碼閱讀(八).ngx_events_module模組
前言
在上一小節中我們對模組的整體有了一定的把握,本小節將進入到事件模組的分析中,瞭解nginx是如何收集、管理、分發事件的。nginx將網路事件以及定時事件整合到一起進行管理,由於各平臺的I/O多路複用機制不同,但是nginx支援多個作業系統,因此在事件模組中也實現了多種針對不同平臺下封裝I/O多路複用機制的模組。由於我所用的環境主要關注的是linux,因此後面主要分析ngx_epoll_module
。
事件模組具體化的通用性介面
前面說過,每一個模組都會遵循ngx_module_t
通用性介面,裡面有個ctx
成員,它是一個泛型指標,可以轉換為其它任何型別,因為它的存在可以讓各型別模組的介面更加具體化。
對應於事件模組,則是ngx_event_module_t
:
typedef struct {
//核心模組名字
ngx_str_t *name;
//在解析配置項前,呼叫該方法建立用於儲存配置項的資料結構
void *(*create_conf)(ngx_cycle_t *cycle);
//在解析配置項後,呼叫該方法用於處理當前事件模組感興趣的配置項
char *(*init_conf)(ngx_cycle_t *cycle, void *conf);
//對於I/O多路複用機制,每個事件模組需要實現的介面
ngx_event_actions_t actions;
} ngx_event_module_t;
ngx_event_actions_t:
typedef struct {
//將事件新增到I/O多路複用機制中
ngx_int_t (*add)(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags);
//將事件從I/O多路複用機制的監聽中移除
ngx_int_t (*del)(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags);
// 啟用事件,目前並沒有使用
ngx_int_t (*enable)(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags);
//禁用事件,目前並沒有使用
ngx_int_t (*disable)(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags);
//新增一個新連線到I/O多路複用機制中(這意味著該連線對應的讀和寫事件也已經新增到了該I/O多路複用機制中了)
ngx_int_t (*add_conn)(ngx_connection_t *c);
//從I/O多路複用機制中移除一個連線的讀寫事件
ngx_int_t (*del_conn)(ngx_connection_t *c, ngx_uint_t flags);
//在多執行緒下使用,nginx目前並沒有以多執行緒的方式執行
ngx_int_t (*process_changes)(ngx_cycle_t *cycle, ngx_uint_t nowait);
//通過該方法來處理事件。會被ngx_process_events_and_timers呼叫
//它是處理以及分發事件的核心
ngx_int_t (*process_events)(ngx_cycle_t *cycle, ngx_msec_t timer,
ngx_uint_t flags);
//初始化事件驅動模組(例如ngx_epoll_module模組)
ngx_int_t (*init)(ngx_cycle_t *cycle, ngx_msec_t timer);
//退出事件模組驅動前呼叫的方法
void (*done)(ngx_cycle_t *cycle);
} ngx_event_actions_t;
ngx_event_module_t
有了ngx_event_actions_t
這個成員,我們就可以把所有的事件驅動模組統一起來了,無論是使用epoll、poll或者kqueue,對於使用事件模組的其他模組來說都不重要。
ngx_events_module所做的工作
要知道核心模組中的模組與其他型別的模組其實有一定的聯絡,我們可以讓核心模組中模組對另外一個型別的模組進行初始化等工作,而其他比較具體的工作,就讓另外型別的模組來完成,這樣就可以讓主框架更加簡化(只關注核心模組),加強模組化的設計。
ngx_events_module
的定義,實現ngx_module_t
通用介面(event/ngx_event.c):
ngx_module_t ngx_events_module = {
/* NGN_MODULE_V1在core/ngx_conf_file.h中巨集定義
* #define NGX_MODULE_V1 0, 0, 0, 0, 0, 0, 1
* 相當於將ctx_index、index、spare0-3、version這幾個成員賦值了
*/
NGX_MODULE_V1,
/* 該成員對應於ctx
* 即核心模組的具體化介面
*/
&ngx_events_module_ctx, /* module context */
/* 該成員指定了模組處理配置項的方法 */
ngx_events_commands, /* module directives */
/* 模組的型別,為核心模組 */
NGX_CORE_MODULE, /* module type */
NULL, /* init master */
NULL, /* init module */
NULL, /* init process */
NULL, /* init thread */
NULL, /* exit thread */
NULL, /* exit process */
NULL, /* exit master */
/* NGX_MODULE_V1_PADDING同樣也在core/ngx_conf_file.h中定義
* #define NGX_MODULE_V1_PADDING 0, 0, 0, 0, 0, 0, 0, 0
* 之前展開ngx_module_t時說過這幾個成員起佔位作用,方便後面進行擴充套件
*/
NGX_MODULE_V1_PADDING
};
關於ngx_events_module_ctx
,它同樣也在event/ngx_event.c中定義:
static ngx_core_module_t ngx_events_module_ctx = {
//name
ngx_string("events"),
//create_conf
NULL,
//init_conf
NULL
};
關於ngx_events_commands
,同樣也在event/ngx_event.c中定義:
static ngx_command_t ngx_events_commands[] = {
{ ngx_string("events"), //name
NGX_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_NOARGS, //type
ngx_events_block, //set
0, //conf
0, //offset
NULL //post },
ngx_null_command
};
知道了這一系列的定義之後,我們可以來分析一下ngx_events_module
模組是通過什麼來替事件模組做一些初始化工作以及在什麼時候做的。
首先我們看到ngx_events_module_ctx
中的create_conf
和init_conf
成員都賦為NULL,這證明該模組並不需要自己儲存一些配置項,因此在main
函式中遍歷ngx_modules
陣列呼叫create_conf
/init_conf
時並不關ngx_events_module
的事。
那麼我們下一個關注點就應該放在commands
成員中的set
成員上了,它是一個函式指標,當解析了nginx.conf
配置檔案後會被呼叫。
還記得是什麼時候開始解析的nginx.conf
檔案嗎?
之前在第二節分析啟動流程時,在ngx_init_cycle
函式中會呼叫ngx_conf_parse
對nginx.conf
檔案進行第一次解析,其中會呼叫ngx_conf_handler
,對讀取到的配置項遍歷ngx_modules
陣列,如果有模組對該配置項感興趣,則會呼叫其commands
成員中的set
指向的函式對該配置項進行處理。
因此,ngx_events_block
函式將在ngx_conf_parse
掃描nginx.conf
檔案掃描到”events {}”時,進入到ngx_conf_handler
被呼叫。這一切都是在ngx_init_cycle
中完成的。
瞭解了這些,就已經得知ngx_events_module
是通過ngx_events_block
函式替事件模組做初始化工作並且是在ngx_init_cycle
中通過ngx_conf_parse
中的ngx_conf_handler
裡遍歷ngx_modules
陣列最終呼叫commands
成員的set
成員指向的函式完成。
可能有點繞,這裡附上呼叫圖(花的有點醜):
接下來,我們就只需要知道ngx_events_block
做了什麼就明白核心模組中的ngx_events_module
為事件模組所做的全部工作了。
該函式的原始碼在event/ngx_event.c
中。
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;
/* count the number of the event modules and set up their indices */
ngx_event_max_module = 0;
//遍歷ngx_modules陣列
for (i = 0; ngx_modules[i]; i++) {
//過濾掉type不為NGX_EVENT_MODULE的模組
if (ngx_modules[i]->type != NGX_EVENT_MODULE) {
continue;
}
/* 初始化ctx_index
* ctx_index為該型別模組中的模組內部順序
* 第一個是ngx_event_core_module
* 畢竟它要負責選擇事件驅動模組
*/
ngx_modules[i]->ctx_index = ngx_event_max_module++;
}
/* 分配指標陣列,儲存所有事件模組生成的配置項結構體指標
* 也就是說,所有事件模組生成的配置項結構體指標會被ngx_events_module模組統一起來,形成一個指標陣列
* 然後ngx_cycle_t中的conf_ctx儲存著每個核心模組的配置結構體指標,該指標就指向了上面形成的指標陣列
*/
//申請指向指標陣列的指標的空間
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_events_module中的ctx成員
* 想象一下,ngx_events_module模組需要儲存指向所有事件模組的配置項結構體指標形成的指標陣列的指標
* 但是介紹ngx_module_t介面時其中好像並沒有專門的成員去儲存它
* 不,其實有。ctx成員就承擔了這個角色,它之前是指向ngx_core_module_t的,但是現在它的工作發生了變化!
* 它改為儲存指向所有事件模組的配置項結構體指標形成的指標陣列的指標了!也就是該函式中的ctx
* 這一點需要特別注意!如果沒有理解到,建議再多看幾遍!
* 這樣ngx_cycle_t中的conf_ctx與所有事件模組的配置項結構體都聯絡起來了!
* 這一部分全是指標,希望你不會看暈
*/
*(void **) conf = ctx;
//遍歷ngx_modules陣列
for (i = 0; ngx_modules[i]; i++) {
//過濾掉非NGX_EVENT_MODULE模組
if (ngx_modules[i]->type != NGX_EVENT_MODULE) {
continue;
}
//指向事件模組所具有的通用介面ngx_event_module_t
m = ngx_modules[i]->ctx;
/* 呼叫其中的create_conf方法
* 建立儲存配置項的結構體指標
*/
if (m->create_conf) {
//將該結構體指標儲存起來
(*ctx)[ngx_modules[i]->ctx_index] = m->create_conf(cf->cycle);
if ((*ctx)[ngx_modules[i]->ctx_index] == NULL) {
return NGX_CONF_ERROR;
}
}
}
//為所有的事件模組解析nginx.conf配置檔案
pcf = *cf;
cf->ctx = ctx;
cf->module_type = NGX_EVENT_MODULE;
cf->cmd_type = NGX_EVENT_CONF;
/* ngx_conf_parse內部會讀取配置檔案
* 若找到對當前讀取到的配置項感興趣的模組
* 則會呼叫該模組中通用接口裡commands成員中set指向的函式
* 進行配置項的解析
*/
rv = ngx_conf_parse(cf, NULL);
*cf = pcf;
if (rv != NGX_CONF_OK)
return rv;
//接著呼叫所有事件模組的init_conf方法
//將各事件模組對應的儲存配置項的結構體成員中還未設立初值的設定為預設值
for (i = 0; ngx_modules[i]; i++) {
if (ngx_modules[i]->type != NGX_EVENT_MODULE) {
continue;
}
m = ngx_modules[i]->ctx;
if (m->init_conf) {
rv = m->init_conf(cf->cycle, (*ctx)[ngx_modules[i]->ctx_index]);
if (rv != NGX_CONF_OK) {
return rv;
}
}
}
return NGX_CONF_OK;
}
簡單的來說,該函式大致做了以下工作:
1. 初始化所有事件模組的ctx_index
序號。(ctx_index
是所有模組都必須實現的ngx_module_t
介面中的成員,代表某種型別模組中內部模組的序號,它可以用於獲取配置項結構體指標)
2. 建立儲存所有事件模組生成的配置項結構體指標的資料結構,指標陣列
3. 呼叫所有事件模組的create_conf方法,產生的結構體指標就儲存在第2步產生的資料結構中
4. 替所有的事件模組解析nginx.conf檔案,當在nginx.conf檔案中發現”event {}”這種事件模組感興趣的配置項時,會回撥所有事件模組ngx_module_t
中的ngx_command_t commands
成員實現的配置項解析的方法
5. 在解析完了配置項之後,呼叫所有事件模組都會實現的init_conf
方法(注意是事件模組具體化的介面而不是全部模組的介面)
conf_ctx如何獲取到事件模組的配置項結構體指標
如果你明白了上面的內容,那這個問題就比較簡單了。
ngx_events_module
定義了一個巨集來完成這個功能。
在event/ngx_event.h中定義:
#define ngx_event_get_conf(conf_ctx, module) \
(*(ngx_get_conf(conf_ctx, ngx_events_module))) [module.ctx_index];
ngx_get_conf
也是巨集函式,在core/ngx_conf_file.h中定義:
#define ngx_get_conf(conf_ctx, module) conf_ctx[module.index]
只用傳入conf_ctx
以及該模組的ngx_module_t
通用介面,就可以獲取到該模組的配置項結構體指標。這裡需要明確ctx_index
和index
的區別,前面已經提過很多次了,這裡再說一遍,ctx_index
是該型別模組中各模組的內部序號,而index
則是所有模組的序號。
ngx_event_get_conf
展開之後是這樣的:(*(conf_ctx[ngx_events_module.index])) [module.ctx_index];
,解釋一下,(*(conf_ctx[module.index]))
獲取到ngx_events_module
核心模組指向事件模組的配置項結構體指標的指標陣列,然後通過傳入的module
,獲取到具體模組在事件模組中的序號,然後取值,就自然獲取到了該模組的配置項結構體指標。
可能說起來有點抽象,這裡引用《深入理解Nginx》書中的圖來幫助理解:
小結
到此為止,我們已經明白了核心模組中的ngx_events_module模組與事件模組產生的聯絡,以及它做的前期工作還有如何獲取到事件模組中的配置項結構體指標,可能理解起來不是那麼容易,因為既涉及到了主框架也涉及到了模組,建議如果不理解的話多看幾遍,書上所講的比較寬泛,沒有太具體到程式碼。
下面我們就可以真正進入到事件模組之中了。