搭建nginx服務器時,主要的配置文件 nginx.conf 是部署和維護服務器人員經常要使用到的文件, 里面進行了許多服務器參數的設置。那么nginx 以模塊 module為骨架的設計下是如何運用模塊 module來解析並執行nginx.conf配置文件下的指令的呢?在探究源碼之前,需要對nginx下的模塊 module 有個基本的認知(詳情參考前面的博文 Nginx 源碼分析-- 淺談對模塊module 的基本認知 )同時也要對nginx中常用到的一些結構有個基本的了解如: 內存池pool 管理相關的函數、ngx_string 的基本結構等(詳情參考前面的博文),若不然看代碼的時候可能不能很明晰其中的意思,本文着重探究的是解析執行的流程。
1、從main函數說起。
Nginx的main函數在nginx.c文件中(本文使用release-1.3.0版本源碼 ,200行),因為是主函數其中涉及到了許許多多的功能模塊的初始化等內容,我們只關注我們需要的部分。看到326行:
ngx_max_module = 0; for (i = 0; ngx_modules[i]; i++) { ngx_modules[i]->index = ngx_max_module++; }
cycle = ngx_init_cycle(&init_cycle);
可以看出來,這里對 ngx_modules ( Nginx 源碼分析-- 淺談對模塊module 的基本認知 中有介紹)進行了索引編號,並且計算得到模塊的總數 ngx_max_module。然后,對cycle進行初始化,跳轉到 ngx_init_cycle中。對於cycle 這個變量是nginx的核心變量,可以說模塊機制都是圍繞它進行的,里面的參數比較復雜涉及到的內容十分多,本文並不詳細對它討論,可以將其看作是一個核心資源庫。
2、ngx_init_cycle 函數
這個函數在文件ngx_cycle.c中(43行),這個函數是nginx初始化中最重要的函數之一,里面涉及到與cycle變量相關的初始化工作,看到第188行
cycle->conf_ctx = ngx_pcalloc(pool, ngx_max_module * sizeof(void *));
這里獲取了 ngx_max_module 個指針空間,用來保存每個模塊的配置信息,從cycle 變量的字段conf_ctx 命名中就可以知道,ctx 為context 上下文的縮寫。接下來看到,下面這段:
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; } }
意思就是獲取模塊中屬於 NGX_CORE_MODULE 類的模塊,如果需要創建配置信息就創建相應的配置信息,並且將地址保存在先前創建好的 cycle->conf_ctx 地址空間中,完成核心模塊配置文件的創建過程,至此前期工作就基本完成了。
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; #if 0 log->log_level = NGX_LOG_DEBUG_ALL; #endif if (ngx_conf_param(&conf) != NGX_CONF_OK) { environ = senv; ngx_destroy_cycle_pools(&conf); return NULL; } if (ngx_conf_parse(&conf, &cycle->conf_file) != NGX_CONF_OK) { environ = senv; ngx_destroy_cycle_pools(&conf); return NULL; }
前面conf的賦值那段,無非是對conf進行些必要的初始化。注意一下這里解析的都是對核心模塊進行的,創建的配置文件也只是核心模塊。關鍵的函數開始出現了:ngx_conf_param(&conf) 將conf需要的參數(可能沒有就是空)存到conf中,ngx_conf_parse(&conf, &cycle->conf_file) 解析配置文件!
3、函數ngx_conf_parse 指令解析函數,關鍵函數!
char * ngx_conf_parse(ngx_conf_t *cf, ngx_str_t *filename) { char *rv; ngx_fd_t fd; ngx_int_t rc; ngx_buf_t buf; ngx_conf_file_t *prev, conf_file; enum { parse_file = 0, parse_block, parse_param } type; /*
該函數存在三種運行方式,並非一定需要打開配置文件
*/ #if (NGX_SUPPRESS_WARN) fd = NGX_INVALID_FILE; prev = NULL; #endif /*
filename 的值為 nginx.conf 的路徑
*/ if (filename) { /* 打開配置文件 */ fd = ngx_open_file(filename->data, NGX_FILE_RDONLY, NGX_FILE_OPEN, 0); ...
/*
保存cf->conf_file 的上文
*/ prev = cf->conf_file;
/*
定義cf->conf_file 當前的變量信息
*/
cf->conf_file = &conf_file; /*
接下來是對,conf_file 的參數進行設置,為了方便閱讀省略此處代碼
*/
...
/*
將函數的運行模式定位為 parse_file ,配置文件模式。
*/
type = parse_file;
/*
其它兩個else 是定義其他模式,在解析nginx.conf時並不會使用到
*/ } else if (cf->conf_file->file.fd != NGX_INVALID_FILE) { type = parse_block; } else { type = parse_param; } /*
完成對配置文件信息的,初步設置之后,就開始對配置文件進行解析。
*/
for ( ;; ) {
/*
獲取從配置文件nginx.conf中讀取的指令名,對於 ngx_conf_read_token 下面給出來返回參數的詳細英文注釋
*/
rc = ngx_conf_read_token(cf); /* * ngx_conf_read_token() may return * * NGX_ERROR there is error * NGX_OK the token terminated by ";" was found * NGX_CONF_BLOCK_START the token terminated by "{" was found * NGX_CONF_BLOCK_DONE the "}" was found * NGX_CONF_FILE_DONE the configuration file is done */
/*
如果錯誤,調轉到done處執行
*/ if (rc == NGX_ERROR) { goto done; }
/*
如果如到“}”符號,跳轉到done處執行,出現錯誤跳到failed處
*/ if (rc == NGX_CONF_BLOCK_DONE) { if (type != parse_block) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "unexpected \"}\""); goto failed; } goto done; }
/*
如果配置文件全部解析完成,調轉到done處執行。
*/ if (rc == NGX_CONF_FILE_DONE) { if (type == parse_block) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "unexpected end of file, expecting \"}\""); goto failed; } goto done; }
/*
如果遇到“{"但出現錯誤,調轉到failed 處執行
*/ if (rc == NGX_CONF_BLOCK_START) { if (type == parse_param) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "block directives are not supported " "in -g option"); goto failed; } } /*
前面對可能出現的情況都進行了相應的跳轉,那么剩下的就是讀取 指令正確后執行的過程了,主要分為兩種,一種為NGX_OK 一般指令的進行如:worker_processes
另一種 NGX_CONF_BLOCK_START 就是以{作為結束符指令的執行,如:events、http 這類有二級指令的。
rc == NGX_OK || rc == NGX_CONF_BLOCK_START
*/ if (cf->handler) { /*
指令執行前是否要進行些處理工作 * the custom handler, i.e., that is used in the http's * "types { ... }" directive */
rv = (*cf->handler)(cf, NULL, cf->handler_conf); if (rv == NGX_CONF_OK) { continue; } if (rv == NGX_CONF_ERROR) { goto failed; } ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, rv); goto failed; } /*
下一個關鍵函數 ngx_conf_handler
*/
rc = ngx_conf_handler(cf, rc); if (rc == NGX_ERROR) { goto failed; } } failed: rc = NGX_ERROR; done: /*
一些完成后的處理,釋放資源或者 出錯處理。省略
*/ ...
/*
恢復上下文
*/
cf->conf_file = prev; } if (rc == NGX_ERROR) { return NGX_CONF_ERROR; } return NGX_CONF_OK; }
在以上代碼中,除了將關鍵函數用紅色標記以外,還特意將 函數中 對上下文的保存和還原 工作的地方進行了紅色標記,因為在nginx源碼中經常使用到這種機制,可以記住下這樣的寫法。
4、函數ngx_conf_handler 指令處理函數,關鍵函數!
static ngx_int_t ngx_conf_handler(ngx_conf_t *cf, ngx_int_t last) { ...
for (i = 0; ngx_modules[i]; i++) { /* 查找與指令想對應的模塊 module*/ if (ngx_modules[i]->type != NGX_CONF_MODULE && ngx_modules[i]->type != cf->module_type) { continue; } /*
讀取模塊的指令集
*/
cmd = ngx_modules[i]->commands; if (cmd == NULL) { continue; } for ( /* void */ ; cmd->name.len; cmd++) { /*
遍歷指令集中的指令,並找尋 從配置文件中讀取到的 指令相對應的 內容
*/
if (name->len != cmd->name.len) { continue; } if (ngx_strcmp(name->data, cmd->name.data) != 0) { continue; } /* 判斷下指令類型 是否正確*/ if (!(cmd->type & cf->cmd_type)) { if (cmd->type & NGX_CONF_MULTI) { multi = 1; continue; } goto not_allowed; }
...
/*判斷指令參數是否正確*/ if (!(cmd->type & NGX_CONF_ANY)) { if (cmd->type & NGX_CONF_FLAG) { if (cf->args->nelts != 2) { goto invalid; } } else if (cmd->type & NGX_CONF_1MORE) {
}
...
} /*
通過指令的類型,來設置執行指令時需要的 模塊前期創建的 cf_ctx里面的配置信息,朔源就是 cycle->conf_ctx 當然它指向的 上下文 可能已經發生了改變
*/
conf = NULL;
if (cmd->type & NGX_DIRECT_CONF) { conf = ((void **) cf->ctx)[ngx_modules[i]->index]; }
...
/*
執行指令對應的 功能函數!!
*/ rv = cmd->set(cf, cmd, conf);
/*
如果執行成功,返回 成功。
*/
if (rv == NGX_CONF_OK) {
return NGX_OK;
}
/*
至此,配置文件的指令執行就結束了。后面都是一些出錯處理,在此省略。
*/ ... } } ... }
寫到這里時間已經有些晚了,小結一下。通過代碼摘錄的介紹,將整個nginx.conf解析的流程 概括的演示了出來,對於其中的些地方可能還不明晰如:二級模塊的指令是如何執行的(就是 events{ ... }、http{ ... } 括號里面的指令如何執行的)、非核心模塊是如何加入對 nginx.conf 這個配置文件進行解析 等一些內容,在后面的分析中再寫吧。晚安!