歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux編程 >> Linux編程 >> Nginx情景分析之配置文件解析

Nginx情景分析之配置文件解析

日期:2017/3/1 11:08:46   编辑:Linux編程

現在針對nginx源碼分析的blog和文章已經很多了,之前我也看過不少,大家的分析都很不錯。在這裡,我不想寫太多重復的內容,只是針對在我分析代碼和查閱blog的過程中,發現的一些比較晦澀或者某些細節有待展開討論的地方,給出我的自己理解和看法,希望跟大家交流和學習。標題為情景分析,目的是向許多情景分析經典(如linux內核情景分析)致敬,力求做到深入深刻。

使用的nginx版本是nginx-1.0.6,我最開始看的代碼是0.7.62,新的版本在功能和穩定性上做了很多的工作。

在分析的時候,我盡量簡單明了,不太重要的地方一帶而過,具體地大家可以去讀代碼。相對復雜或者晦澀的地方,將詳細展開。希望大家認同我的一個觀點:我容易看得懂的地方,大家也不難看懂;而我認為比較難懂的地方,通常情況下大家讀起來也不會太省力!

首先我們從配置文件開始,下面的分析是建立在網友對nginx的配置文件結構有大概熟悉為前提,這樣才可以很好的理解代碼。

這裡有必要提醒一點:原始代碼中ngx_modules你找不到它的定義和初始化,要看到它,你必須執行configure,make,在原來的代碼目錄下會出現一個objs文件夾,裡面的3個文件ngx_auto_config.h,ngx_auto_headers.h,ngx_modules.c,需要在建source insight工程時也包含進去,這樣有利於我們把握整個代碼結構。呵呵,有意思的是,nginx的configure文件是作者手工寫的,裡面有許多管理代碼工程的方法,有時間的話,也是值得學習下的。

1.ngx_cycle_t *ngx_init_cycle(ngx_cycle_t *old_cycle);

配置文件的解析相關的處理主要在ngx_init_cycle函數中被調用。既然如此,我們就先說說ngx_init_cycle函數吧。

它需要一個參數類型為ngx_cycle_t *,返回值也是一個ngx_cycle_t*,與此同時我們注意到參數名為old_cycle,那麼這個函數的作用是啥呢?很明顯是由old得到一個new。其中ngx_cycle_t的結構保存一些全局的配置和信息。這個函數具體作用將在reconfig(重讀配置文件)的時候得到體現,可以理解為old_cycle是當前正在使用的配置信息,當配置文件做了某些修改之後,ngx_init_cycle通過old_cycle中的一些數據,對new_cycle進行一些設置,在經過進一步的配置解析之後,就可以得到一個new cycle。好了,init cycle的過程就說這麼多,重要的還在後面!

2.char *ngx_conf_parse(ngx_conf_t *cf, ngx_str_t *filename)

當我們使用sourceinsight查看這個函數的調用情況時,會發現調用它的地方很多。其實,入口點就在ngx_init_cycle中對ngx_conf_parse調用,後面的所有的調用可以看作是在此之後的遞歸調用。為什麼會是這個樣子呢?原因在於nginx是一邊讀取配置信息,一邊解析執行相關的處理,具體一點講,就是“讀一行,執行一行”,一行的定義在這裡是指以分號或者是“{”和“}”等結尾的一行,例如:我們解析到http {},我們就調用針對httpblock的處理,在處理的時候我們又會碰到server {},自然就會調用server block的處理。。。以此類推!。

對配置文件的讀取主要在函數ngx_conf_read_token中,www.linuxidc.com這個函數每次會把NGX_CONF_BUFFER(即4KB)大小的配置信息讀到內存buf中,然後對該buf進行分析。在該函數外,主要通過該函數的返回值在做不同的處理,關於它的返回值的含義,大家可以去細細讀下代碼,意義很明確!

配置指令我們通過上面的操作就可以拿到了,以空格分開的各個字符串被保存在一個字符串數組裡(即cf->args),這一點代碼體現的很明顯。之後呢,我們就調用ngx_conf_handler函數來處理當前拿到的這行配置。這裡我們先從總體上說一下ngx_conf_handler這個函數的工作原理:”它遍歷系統中所有的模塊配置,找到特定模塊,並匹配特定命令,然後執行“。下面我們把這句話展開來講,並暴露細節。

nginx會將所有的模塊分類管理,自然各個模塊被劃分到了一個個“集合”中去,同樣一個模塊下的指令也是分類的(如屬於哪類模塊,配置在哪些位置是正確的等等),所以在每次調用ngx_conf_parse的時候都會指出”我這次解析的配置信息是啥類型“,舉例:

conf.module_type= NGX_CORE_MODULE;
conf.cmd_type = NGX_MAIN_CONF;

即解析得到的指令我們將core module類型中查找,並且在找到的module中類型為main conf的指令。

在進入重點之前,我們先看一個地方,就是cf->handler的處理,它是做什麼用的呢?是這樣的,nginx的通用處理函數ngx_conf_handler主要是針對cf->args的字符串數組來使用的,像有的配置,如types,charset_map,並不是單純的字符串數組,指令的參數可能會放在{}中,這樣通用的解析和處理函數就不適用了。www.linuxidc.com通過我們注冊cf->handler,我們就可以對面{}中參數做常規的配置處理了。這是我的理解,我認為是正確的。

好了解析來我們進入到ngx_conf_handler函數中,看看它的工作機理。

3. static ngx_int_t

ngx_conf_handler(ngx_conf_t *cf,ngx_int_t last)

參數last是ngx_conf_read_token解析的返回結果。強調一點的是,cf->args中已經保存了我們需要的各個參數。

接下來的處理分4步走:

(1) 模塊匹配

代碼顯示的很明顯,它首先會根據你指定的類型,對特定的模塊進行查找。

(2) command匹配

找到匹配的模塊之後會遍歷該模塊下的command數組,來從中找到它需要找command信息。如果字面上匹配,還要進行command類型的配置檢查,如果此時類型不匹配,nginx會報告給你。

(3) 參數個數匹配

主要檢查對個數有嚴格要求的command,對於任意個數(即NGX_CONF_ANY)的command,就直接處理了。

關於參數個數,nginx定義了一些宏,如NGX_CONF_TAKEx(x代表1,2,3等,表示可配置一個,兩個,三個參數。。),還有像1MORE,2MORE,意思也很明確。注意的是,對於有個數限制的command,最多可配置的個數為8個,即NGX_CONF_MAX_ARGS,這個在寫自己的module時要注意,我吃過這方面的虧。。。

(4) 執行command(即set函數)。

好了,前面的檢查工作做完之後,就要真正去執行相關的操作,生成module的配置信息了。

這裡涉及command的類型問題,搞清楚它對後面的理解很重要。Let’s go!

NGX_DIRECT_CONF,NGX_MAIN_CONF,NGX_HTTP_MAIN_CONF,NGX_HTTP_SRV_CONF,NGX_HTTP_LOC_CONF等

其中NGX_DIRECT_CONF一般是在http塊等之外的配置, NGX_HTTP_MAIN_CONF是直接配置在http塊中的,NGX_HTTP_SRV_CONF配置在server塊中,NGX_HTTP_LOC_CONF配置在location中等等。

接下來是考驗你c語言指針功底的時候了,呵呵!

我們來看cf->ctx這個成員,它的類型是void *,到這裡你大概會猜到它的用處,肯定是在不同的條件下轉來轉去!

在ngx_init_cycle中我們發現最初cf->ctx的值由cycle->conf_ctx賦值得到,cycle->conf_ctx是一個void ****類型(-_-!最開始的時候我曾經很好奇,這樣的指針到底會用來做什麼?),它的作用我們很快揭曉。

在ngx_init_cycle中有這樣關鍵的一句:

cycle->conf_ctx =ngx_pcalloc(pool, ngx_max_module * sizeof(void *));你看到了嗎?ngx_max_module是系統中所有module的總數,conf_ctx在這一句中用來給每個module占一個位置(一個指針),但是每個位置到底指向啥東西呢?呵呵,反正是個void *,指啥都行,可能對於有的module,我們要拿到一個我們想要的實體,要透過4層的指針(即void ****),你是不是感到很好奇呢?

兜了一圈,我們發現最終這些指針數組的成員賦值都會在這裡的第4步來完成。

這裡分3種情況:

(1) NGX_DIRECT_CONF

對於那些游離於{}之外的配置,一般屬於ngx_core_conf_t的配置內容,在ngx_init_cycle的時候已經對NGX_CORE_MODULE類型的模塊進行了初始化(模塊需要有init函數),這裡根據配置信息,set函數會做配置結構內中成員的賦值。

(2) NGX_MAIN_CONF

這樣的配置包括event,http等,他們沒有init函數,所以實際的空間分配需要在set函數內完成,於是就有了:

conf =&(((void **) cf->ctx)[ngx_modules[i]->index]); //取指針的地址

rv =cmd->set(cf, cmd, conf); // 指針在函數內被賦值

set中conf參數是一個二重指針,這也就有個之後在ngx_http_block中的語句:

ctx =ngx_pcalloc(cf->pool, sizeof(ngx_http_conf_ctx_t));

*(ngx_http_conf_ctx_t **) conf = ctx;

(3)其他

其實這裡的“其他”,主要是一些server和location的類型的command,這些command大量的集中於http的相關配置中。

我們先來看ngx_http_conf_ctx_t結構:

typedefstruct {

void **main_conf;

void **srv_conf;

void **loc_conf;

}ngx_http_conf_ctx_t;

這裡有一點需要交代的是,像http模塊下,有一些所謂的子模塊,而這些子模塊基本上都是http中server或者location中的一些配置。這些配置通過模塊中ctx_index,以數組的形式,將他們的配置結構的指針保存在srv_conf或loc_conf中,這就是他們的類型為什麼會是void **。

我們來看這句:

confp =*(void **) ((char *) cf->ctx + cmd->conf);

我相信這一句是比較難懂的,實際上cmd->conf是成員在結構體中的偏移量。這裡舉個例子就明白了:

假設我們在32位機下,cmd->conf為8,cf->ctx的地址為0x008004A2,那麼cf->ctx+cmd->conf =

0x008004A2+ 8 = 0x008004AA,你應該曉得這個地址裡面放的是什麼,所以外面做了一個類型強轉,(void **),這個轉換告訴我們,0x008004AA是個二級指針值,這個地址下放的是一個指針,現在我們需要裡面的指針,於是就有了*(void **)。

完整的過程是這樣的:

//得到一個指針,到底是幾維呢?無所謂,這裡我們是拿它當二維指針來用的,為啥呢?

// 前面說過了,他們是子模塊配置的指針的數組!

confp =*(void **) ((char *) cf->ctx + cmd->conf);

conf = confp[ngx_modules[i]->ctx_index]; // 拿到配置結構

rv =cmd->set(cf, cmd, conf); // 做處理

最後說明一下,開始提到的那個void ****類型成員,使用這種類型的原因我們可以在ngx_events_block函數中找到答案。

Copyright © Linux教程網 All Rights Reserved