2011年9月9日
admin發(fā)表評(píng)論閱讀評(píng)論 1,744 次瀏覽
請(qǐng)關(guān)注最新修正合訂:
http://lenky.info/ebook/這一系列的文章還是在09年寫(xiě)的,存在電腦里很久了,現(xiàn)在貼出來(lái)。順序也不記得了,看到那個(gè)就發(fā)那個(gè)吧,最近都會(huì)發(fā)上來(lái)。歡迎轉(zhuǎn)載,但請(qǐng)保留鏈接:http://lenky.info/,謝謝。
nginx的配置文件格式是nginx作者自己定義的,并沒(méi)有采用像語(yǔ)法分析生成器LEMON那種經(jīng)典的LALR(1)來(lái)描述配置信息,這樣做的好處就是自由,而壞處就是對(duì)于nginx的每一項(xiàng)配置信息都必須自己去解析,因此我們很容易看到nginx模塊里大量篇幅的配置信息解析代碼,比如模塊ngx_http_core_module。
當(dāng)然,nginx配置文件的格式也不是隨意的,它有自己的一套規(guī)范:
nginx配置文件是由多個(gè)配置項(xiàng)組成的。每一個(gè)配置項(xiàng)都有一個(gè)項(xiàng)目名和對(duì)應(yīng)的項(xiàng)目值,項(xiàng)目名又被稱(chēng)為指令(Directive),而項(xiàng)目值可能簡(jiǎn)單的字符串(以分號(hào)結(jié)尾),也可能是由簡(jiǎn)單字符串和多個(gè)配置項(xiàng)組合而成配置塊的復(fù)雜結(jié)構(gòu)(以大括號(hào)}結(jié)尾),因此我們可以將配置項(xiàng)歸納為兩種:簡(jiǎn)單配置項(xiàng)和復(fù)雜配置項(xiàng)。
從上面這條規(guī)范可以看到這里包含有遞歸的思想,因此在后面的配置解析代碼里可以看到某些函數(shù)被遞歸調(diào)用,其原因也就在這里。
對(duì)于復(fù)雜配置項(xiàng)來(lái)說(shuō),其值是由多個(gè)簡(jiǎn)單/復(fù)雜配置項(xiàng)組成,因此nginx不做過(guò)細(xì)的處理,一般就是申請(qǐng)內(nèi)容空間、切換解析狀態(tài),然后遞歸調(diào)用解析函數(shù);真正將用戶(hù)配置信息轉(zhuǎn)換為nginx內(nèi)變量的值,還是那些簡(jiǎn)單配置項(xiàng)所對(duì)應(yīng)的處理函數(shù)。
不管是簡(jiǎn)單配置項(xiàng)還是復(fù)雜配置項(xiàng),它們的項(xiàng)目名和項(xiàng)目值都是由標(biāo)記(token:這里指一個(gè)配置文件字符串內(nèi)容中被空格、引號(hào)、括號(hào),比如’{‘、換行符等分割開(kāi)來(lái)的字符子串。)組成的,配置項(xiàng)目名就是一個(gè)token,而配置項(xiàng)目值可以是一個(gè)、兩個(gè)、多個(gè)token組成。
比如簡(jiǎn)單配置項(xiàng):
daemon off;
其項(xiàng)目名daemon為一個(gè)token,項(xiàng)目值off也是一個(gè)token。而簡(jiǎn)單配置項(xiàng):
error_page 404 /404.html;
其項(xiàng)目值就包含有兩個(gè)token,分別為404和/404.html。
對(duì)于復(fù)雜配置項(xiàng):
location /www {
index index.html index.htm index.php;
}
其項(xiàng)目名location為一個(gè)token,項(xiàng)目值是一個(gè)token(/www)和多條簡(jiǎn)單配置項(xiàng)組成的復(fù)合結(jié)構(gòu)。
前面將token解釋為一個(gè)配置文件字符串內(nèi)容中被空格、引號(hào)、括號(hào),比如’{‘等分割開(kāi)來(lái)的字符子串,那么很明顯,上面例子中的taken是被空格分割出來(lái),事實(shí)上下面這樣的配置也是正確的:
“daemon” “off”;
‘daemon’ ‘off’;
daemon ‘off’;
“daemon” off;
當(dāng)然,一般情況下沒(méi)必要這樣費(fèi)事去加些引號(hào),除非我們需要在token內(nèi)包含空格而又不想使用轉(zhuǎn)義字符(\)的話(huà)就可以利用引號(hào),比如:
log_format main ‘$remote_addr – $remote_user [$time_local] $status ‘
‘”$request” $body_bytes_sent “$http_referer” ‘
‘”$http_user_agent” “$http_x_forwarded_for”‘;
但是像下面這種格式就會(huì)有問(wèn)題,這對(duì)于我們來(lái)說(shuō)很容易理解,不多詳敘:
“daemon “off”;
對(duì)于如此多的配置項(xiàng),nginx怎樣去解析它們呢?在什么時(shí)候去解析呢?事實(shí)上,對(duì)于nginx所有可能出現(xiàn)的配置項(xiàng)(通過(guò)項(xiàng)目名即指令Directive去判斷),nginx都會(huì)提供有對(duì)應(yīng)的代碼去解析它,如果配置文件內(nèi)出現(xiàn)了nginx無(wú)法解析的配置項(xiàng),那么nginx將報(bào)錯(cuò)并直接退出程序。
舉例來(lái)說(shuō),對(duì)于配置項(xiàng)daemon,在模塊ngx_core_module的命令解析數(shù)組內(nèi)的第一項(xiàng)就是保存的對(duì)該配置項(xiàng)進(jìn)行解析所需要的信息,比如daemon配置項(xiàng)的類(lèi)型,執(zhí)行實(shí)際解析操作的回調(diào)函數(shù),解析出來(lái)的配置項(xiàng)值所存放的地址等:
{ ngx_string(“daemon”),
NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_FLAG,
ngx_conf_set_flag_slot,
0,
offsetof(ngx_core_conf_t, daemon),
NULL },
而如果我在配置文件內(nèi)加入如下配置內(nèi)容:
lenky on;
啟動(dòng)nginx,直接返回錯(cuò)誤,這是因?yàn)閷?duì)于lenky指令,nginx沒(méi)有對(duì)應(yīng)的代碼去解析它:
[emerg]: unknown directive “l(fā)enky” in /usr/local/nginx/conf/nginx.conf:2
上面給出的解析daemon配置項(xiàng)的數(shù)據(jù)類(lèi)型為ngx_command_s結(jié)構(gòu)體類(lèi)型,該結(jié)構(gòu)體類(lèi)型對(duì)所有的nginx配置項(xiàng)進(jìn)行了統(tǒng)一的描述:
typedef struct ngx_command_s ngx_command_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;
};
一個(gè)ngx_command_s結(jié)構(gòu)體類(lèi)型的元素用于解析并獲取一項(xiàng)nginx配置,其中字段name指定獲取的配置項(xiàng)目名稱(chēng),字段set指向一個(gè)回調(diào)函數(shù),該函數(shù)執(zhí)行解析并獲取配置項(xiàng)值的操作;而type指定該配置項(xiàng)的相關(guān)信息,比如:
1,該配置的類(lèi)型:NGX_CONF_FLAG表示該配置項(xiàng)目有一個(gè)布爾類(lèi)型的值,例如”daemon”就是一個(gè)布爾類(lèi)型配置項(xiàng),值為”on”或”off”;NGX_CONF_BLOCK表示該配置項(xiàng)目有一個(gè)塊類(lèi)型的值,比如配置項(xiàng)”http”、”events”等。
2,該配置接收的參數(shù)個(gè)數(shù):NGX_CONF_NOARGS、NGX_CONF_TAKE1、NGX_CONF_TAKE2、……、NGX_CONF_TAKE7,分別表示該配置項(xiàng)沒(méi)有參數(shù)、一個(gè)、兩個(gè)、七個(gè)參數(shù)。
3,該配置的位置域:NGX_MAIN_CONF、NGX_HTTP_MAIN_CONF、NGX_EVENT_CONF、NGX_HTTP_SRV_CONF、NGX_HTTP_LOC_CONF、NGX_HTTP_UPS_CONF等等。
字段conf被NGX_HTTP_MODULE類(lèi)型模塊所用,該字段指定當(dāng)前配置項(xiàng)所在的大致位置,取值為NGX_HTTP_MAIN_CONF_OFFSET、NGX_HTTP_SRV_CONF_OFFSET、NGX_HTTP_LOC_CONF_OFFSET三者之一;其它模塊不用該字段,直接指定為0。
字段offset指定該配置項(xiàng)值的精確存放位置,一般指定為某一個(gè)結(jié)構(gòu)體變量的字段偏移。也有那種塊配置項(xiàng),例如”server”,它不用保存配置項(xiàng)值,或者說(shuō)無(wú)法保存,或者說(shuō)其值被分得更細(xì)小而被保存起來(lái),此時(shí)字段offset也指定為0即可。
字段post在大多數(shù)情況下為NULL,但在某些特殊配置項(xiàng)中也會(huì)指定值,而且多為回調(diào)函數(shù)指針,例如auth_basic、connection_pool_size、request_pool_size、optimize_host_names、client_body_in_file_only等配置項(xiàng)。
對(duì)于配置文件的格式以及配置項(xiàng)在nginx中的封裝基本就描述到這,下面開(kāi)始對(duì)整個(gè)nginx配置信息的解析流程進(jìn)行描述。
假設(shè)我們以命令:
nginx -c /usr/local/nginx/conf/nginx.conf
啟動(dòng)nginx,而配置文件nginx.conf也比較簡(jiǎn)單,如下所示:
worker_processes 2;
error_log logs/error.log debug;
events {
use epoll;
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
server {
listen 8888;
server_name localhost;
location / {
root html;
index index.html index.htm;
}
error_page 404 /404.html;
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
}
下面就來(lái)描述nginx是如何將這些配置信息轉(zhuǎn)化為nginx內(nèi)各對(duì)應(yīng)變量的值以控制nginx工作的。
首先,抹掉一些細(xì)節(jié),我們跟著nginx的啟動(dòng)流程進(jìn)入到與配置信息相關(guān)的函數(shù)調(diào)用處:
main–>ngx_init_cycle–>ngx_conf_parse:
if (ngx_conf_parse(&conf, &cycle->conf_file) != NGX_CONF_OK) {
environ = senv;
ngx_destroy_cycle_pools(&conf);
return NULL;
}
此處調(diào)用ngx_conf_parse傳入了兩個(gè)參數(shù),第一個(gè)參數(shù)為ngx_conf_s變量,而第二個(gè)參數(shù)就是保存的配置文件路徑字符串“/usr/local/nginx/conf/nginx.conf”。ngx_conf_parse函數(shù)是執(zhí)行配置解析的關(guān)鍵函數(shù),其原型如下:
char * ngx_conf_parse(ngx_conf_t *cf, ngx_str_t *filename);
它是一個(gè)間接的遞歸函數(shù),也就是說(shuō)雖然我們?cè)谠摵瘮?shù)體內(nèi)看不到直接的對(duì)其本身的調(diào)用,但是它執(zhí)行的一些函數(shù)(比如ngx_conf_handler)內(nèi)又會(huì)調(diào)用ngx_conf_parse函數(shù),因此形成遞歸,這一般在處理一些特殊配置指令或復(fù)雜配置項(xiàng),比如指令include、events、http、server、location等的處理時(shí)。
ngx_conf_parse函數(shù)體代碼量不算太多,但是它也將配置內(nèi)容的解析過(guò)程分得很清楚,總體來(lái)看分成三個(gè)步驟:1,區(qū)分當(dāng)前解析狀態(tài);2,讀取配置標(biāo)記token;3,當(dāng)讀取了合適數(shù)量的標(biāo)記token之后對(duì)其進(jìn)行實(shí)際的處理,轉(zhuǎn)換為nginx內(nèi)變量的值。
當(dāng)執(zhí)行到ngx_conf_parse函數(shù)內(nèi)時(shí),配置的解析可能處于三種狀態(tài):
第一種,剛開(kāi)始解析一個(gè)配置文件,即此時(shí)的參數(shù)filename指向一個(gè)配置文件路徑字符串,需要函數(shù)ngx_conf_parse打開(kāi)該文件并獲取相關(guān)的文件信息以便下面代碼讀取文件內(nèi)容并進(jìn)行解析,除了在上面介紹的nginx啟動(dòng)時(shí)開(kāi)始主配置文件解析時(shí)屬于這種情況,還有當(dāng)遇到include指令時(shí)也將以這種狀態(tài)調(diào)用ngx_conf_parse函數(shù),因?yàn)閕nclude指令表示一個(gè)新的配置文件要開(kāi)始解析。狀態(tài)標(biāo)記為type = parse_file;。
第二種,開(kāi)始解析一個(gè)配置塊,即此時(shí)配置文件已經(jīng)打開(kāi)并且也已經(jīng)對(duì)文件部分進(jìn)行了解析,當(dāng)遇到復(fù)雜配置項(xiàng)比如events、http等時(shí),這些復(fù)雜配置項(xiàng)的處理函數(shù)又會(huì)遞歸的調(diào)用ngx_conf_parse函數(shù),此時(shí)解析的內(nèi)容還是來(lái)自當(dāng)前的配置文件,因此無(wú)需再次打開(kāi)它,狀態(tài)標(biāo)記為type = parse_block;。
第三種,開(kāi)始解析配置項(xiàng),這在對(duì)用戶(hù)通過(guò)命令行-g參數(shù)輸入的配置信息進(jìn)行解析時(shí)處于這種狀態(tài),如:
nginx -g ‘daemon on;’
nginx在調(diào)用ngx_conf_parse函數(shù)對(duì)配置信息’daemon on;’進(jìn)行解析時(shí)就是這種狀態(tài),狀態(tài)標(biāo)記為type = parse_param;。
前面說(shuō)過(guò),nginx配置是由標(biāo)記組成的,在區(qū)分好了解析狀態(tài)之后,接下來(lái)就要讀取配置內(nèi)容,而函數(shù)ngx_conf_read_token就是做這個(gè)事情的:
rc = ngx_conf_read_token(cf);
函數(shù)ngx_conf_read_token對(duì)配置文件內(nèi)容逐個(gè)字符掃描并解析為單個(gè)的token,當(dāng)然,該函數(shù)并不會(huì)頻繁的去讀取配置文件,它每次從文件內(nèi)讀取足夠多的內(nèi)容以填滿(mǎn)一個(gè)大小為NGX_CONF_BUFFER的緩存區(qū)(除了最后一次,即配置文件剩余內(nèi)容本來(lái)就不夠了),這個(gè)緩存區(qū)在函數(shù)ngx_conf_parse內(nèi)申請(qǐng)并保存引用到變量cf->conf_file->buffer內(nèi),函數(shù)ngx_conf_read_token反復(fù)使用該緩存區(qū),該緩存區(qū)可能有如下一些狀態(tài):
初始狀態(tài),即函數(shù)ngx_conf_parse內(nèi)申請(qǐng)后的初始狀態(tài)。
這是在處理過(guò)程中的狀態(tài),有一部分配置內(nèi)容已經(jīng)被解析為一個(gè)個(gè)token并保存起來(lái),而有一部分內(nèi)容正要被組合成token,還有一部分內(nèi)容等待處理。
這是在字符都處理完了,需要繼續(xù)從文件內(nèi)讀取新的內(nèi)容到緩存區(qū)。前面圖示說(shuō)過(guò),已解析字符已經(jīng)沒(méi)用了,因此我們可以將已掃描但還未組成token的字符移動(dòng)到緩存區(qū)的前面,然后從配置文件內(nèi)讀取內(nèi)容填滿(mǎn)緩存區(qū)剩余的空間,情況如下:
如果配置文件內(nèi)容不夠,即最后一次,那么情況就是下面這樣:
函數(shù)ngx_conf_read_token在讀取了合適數(shù)量的標(biāo)記token之后就開(kāi)始下一步驟即對(duì)這些標(biāo)記進(jìn)行實(shí)際的處理。那多少才算是讀取了合適數(shù)量的標(biāo)記呢?區(qū)別對(duì)待,對(duì)于簡(jiǎn)單配置項(xiàng)則是讀取其全部的標(biāo)記,也就是遇到結(jié)束標(biāo)記分號(hào);為止,此時(shí)一條簡(jiǎn)單配置項(xiàng)的所有標(biāo)記都被讀取并存放在cf->args數(shù)組內(nèi),因此可以調(diào)用其對(duì)應(yīng)的回調(diào)函數(shù)進(jìn)行實(shí)際的處理;對(duì)于復(fù)雜配置項(xiàng)則是讀完其配置塊前的所有標(biāo)記,即遇到大括號(hào){為止,此時(shí)復(fù)雜配置項(xiàng)處理函數(shù)所需要的標(biāo)記都已讀取到,而對(duì)于配置塊{}內(nèi)的標(biāo)記將在接下來(lái)的函數(shù)ngx_conf_parse遞歸調(diào)用中繼續(xù)處理,這可能是一個(gè)反復(fù)的過(guò)程。
當(dāng)然,函數(shù)ngx_conf_read_token也可能在其它情況下返回,比如配置文件格式出錯(cuò)、文件處理完(遇到文件結(jié)束)、塊配置處理完(遇到大括號(hào)}),這幾種返回情況的處理都很簡(jiǎn)單,不多詳敘。
對(duì)于簡(jiǎn)單/復(fù)雜配置項(xiàng)的處理,一般情況下,這是通過(guò)函數(shù)ngx_conf_handler來(lái)進(jìn)行的,而也有特殊的情況,也就是配置項(xiàng)提供了自定義的處理函數(shù),比如types指令。函數(shù)ngx_conf_handler也做了三件事情,首先,它需要找到當(dāng)前解析出來(lái)的配置項(xiàng)所對(duì)應(yīng)的ngx_command_s結(jié)構(gòu)體,前面說(shuō)過(guò)該ngx_command_s包含有配置項(xiàng)的相關(guān)信息以及對(duì)應(yīng)的回調(diào)實(shí)際處理函數(shù)。如果沒(méi)找到配置項(xiàng)所對(duì)應(yīng)的ngx_command_s結(jié)構(gòu)體,那么誰(shuí)來(lái)處理這個(gè)配置項(xiàng)呢?自然是不行的,因此nginx就直接進(jìn)行報(bào)錯(cuò)并退出程序。其次,找到當(dāng)前解析出來(lái)的配置項(xiàng)所對(duì)應(yīng)的ngx_command_s結(jié)構(gòu)體之后還需進(jìn)行一些有效性驗(yàn)證,因?yàn)閚gx_command_s結(jié)構(gòu)體內(nèi)包含有配置項(xiàng)的相關(guān)信息,因此有效性驗(yàn)證是可以進(jìn)行的,比如配置項(xiàng)的類(lèi)型、位置、帶參數(shù)的個(gè)數(shù)等等。只有經(jīng)過(guò)了嚴(yán)格有效性驗(yàn)證的配置項(xiàng)才調(diào)用其對(duì)應(yīng)的回調(diào)函數(shù):
rv = cmd->set(cf, cmd, conf);
進(jìn)行處理,這也就是第三件事情。在處理函數(shù)內(nèi),根據(jù)實(shí)際的需要又可能再次調(diào)用函數(shù)ngx_conf_parse,如此反復(fù)直至所有配置信息都被處理完。
下面來(lái)看一個(gè)set回調(diào)函數(shù)的例子,以對(duì)配置指令daemon的解析函數(shù)為例,根據(jù)前面給出的指令daemon對(duì)應(yīng)的ngx_command_s結(jié)構(gòu)體可以看到,其set回調(diào)函數(shù)指向的是函數(shù)ngx_conf_set_flag_slot,該函數(shù)的原型如下:
char * ngx_conf_set_flag_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
這是一個(gè)公共的解析函數(shù),即它并不是單獨(dú)為解析daemon配置指令而存在,而是對(duì)于所有NGX_CONF_FLAG類(lèi)型的配置項(xiàng)都是用的該函數(shù)來(lái)進(jìn)行解析。
源文件:ngx_conf_file.c
char *
ngx_conf_set_flag_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
char *p = conf;
ngx_str_t *value;
ngx_flag_t *fp;
ngx_conf_post_t *post;
/* 解析出來(lái)的對(duì)應(yīng)值存放的內(nèi)存位置 */
fp = (ngx_flag_t *) (p + cmd->offset);
/* 該內(nèi)存位置已有值,故知配置指令重復(fù) */
if (*fp != NGX_CONF_UNSET) {
return “is duplicate”;
}
/* cf->args存放的是與當(dāng)前處理配置項(xiàng)相關(guān)的各個(gè)token,比如解析daemon配置指令時(shí), cf->args內(nèi)的數(shù)據(jù)詳細(xì)如下,以便于理解(通過(guò)gdb調(diào)試獲得的結(jié)果):
(gdb) p *cf->args
$1 = {elts = 0x9a0c798, nelts = 2, size = 8, nalloc = 10, pool = 0x9a0bf00}
(gdb) p *(ngx_str_t*)(cf->args->elts)
$2 = {len = 6, data = 0x9a0c7e8 “daemon”}
(gdb) p *(((ngx_str_t*)(cf->args->elts)+1))
$3 = {len = 3, data = 0x9a0c7f0 “off”}
*/
value = cf->args->elts;
/* 解析,布爾值的配置很好解析,”on”轉(zhuǎn)為nginx內(nèi)的1,”off”轉(zhuǎn)為0。*/
if (ngx_strcasecmp(value[1].data, (u_char *) “on”) == 0) {
*fp = 1;
} else if (ngx_strcasecmp(value[1].data, (u_char *) “off”) == 0) {
*fp = 0;
} else { /* 出錯(cuò)提示 */
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
“invalid value \”%s\” in \”%s\” directive, ”
“it must be \”on\” or \”off\”",
value[1].data, cmd->name.data);
return NGX_CONF_ERROR;
}
/* 其它處理函數(shù),對(duì)于daemon配置指令來(lái)說(shuō)為NULL,但是對(duì)于其它指令,比如optimize_server_names則還需調(diào)用自定義的處理。*/
if (cmd->post) {
post = cmd->post;
return post->post_handler(cf, post, fp);
}
return NGX_CONF_OK;
}
對(duì)于nginx配置文件的解析流程基本就是如此,上面的介紹忽略了很多細(xì)節(jié),前面也說(shuō)過(guò),事實(shí)上對(duì)于配置信息解析的代碼(即各種各樣的回調(diào)函數(shù)cmd->set的具體實(shí)現(xiàn))占去了nginx大幅的源代碼,而我們這里并沒(méi)有做過(guò)多的分析,僅例舉了daemon配置指令的解析過(guò)程,因?yàn)閷?duì)于不同的配置項(xiàng),解析代碼完全是根據(jù)自身應(yīng)用而不同的,當(dāng)然,除了一些可公共出來(lái)的代碼以外。最后,看一個(gè)nginx配置文件解析的流程圖,如下:
nginx自身對(duì)字符串進(jìn)行了封裝,對(duì)應(yīng)的封裝結(jié)構(gòu)體為ngx_str_t,這里說(shuō)明一下,以后類(lèi)似的情況同此。