欧美性猛交XXXX免费看蜜桃,成人网18免费韩国,亚洲国产成人精品区综合,欧美日韩一区二区三区高清不卡,亚洲综合一区二区精品久久

打開(kāi)APP
userphoto
未登錄

開(kāi)通VIP,暢享免費電子書(shū)等14項超值服

開(kāi)通VIP
nginx源碼分析

nginx是一個(gè)開(kāi)源的高性能web服務(wù)器系統,事件驅動(dòng)的請求處理方式和極其苛刻的資源使用方式,使得nginx成為名副其實(shí)的高性能服務(wù)器。 nginx的源碼質(zhì)量也相當高,作者“家釀”了許多代碼,自造了不少輪子,諸如內存池、緩沖區、字符串、鏈表、紅黑樹(shù)等經(jīng)典數據結構,事件驅動(dòng)模型,http解析,各種子處理模塊,甚至是自動(dòng)編譯腳本都是作者根據自己的理解寫(xiě)出來(lái)的,也正因為這樣,才使得nginx比其他的web服務(wù)器更加高效。

nginx 的代碼相當精巧和緊湊,雖然全部代碼僅有10萬(wàn)行,但功能毫不遜色于幾十萬(wàn)行的apache。不過(guò)各個(gè)部分之間耦合的比較厲害,很難把其中某個(gè)部分的實(shí)現拆出來(lái)使用。對于這樣一個(gè)中大型的復雜系統源碼進(jìn)行分析,是有一定的難度的,剛開(kāi)始也很難找到下手的入口,所以做這樣的事情就必須首先明確目標和計劃。

最初決定做這件事情是為了給自己一些挑戰,讓生活更有意思。但看了幾天之后,覺(jué)得這件事情不該這么簡(jiǎn)單看待,這里面有太多吸引人的東西了,值得有計劃的系統學(xué)習和分析。首先這個(gè)系統中幾乎涵蓋了實(shí)現高性能服務(wù)器的各種必殺技,epoll、kqueue、master-workers、pool、 buffer… …,也涵蓋了很多web服務(wù)開(kāi)發(fā)方面的技術(shù),ssi、ssl、proxy、gzip、regex、load balancing、reconfiguration、hot code swapping… …,還有一些常用的精巧的數據結構實(shí)現,所有的東西很主流;其次是一流的代碼組織結構和干凈簡(jiǎn)潔的代碼風(fēng)格,尤其是整個(gè)系統的命名恰到好處,可讀性相當高,很kiss,這種風(fēng)格值得學(xué)習和模仿;第三是通過(guò)閱讀源碼可以感受到作者嚴謹的作風(fēng)和卓越的能力,可以給自己增加動(dòng)力,樹(shù)立榜樣的力量。

另一方面,要達到這些目標難度很高,必須要制定詳細的計劃和采取一定有效的方法。

對于這么大的一個(gè)系統,想一口氣知曉全部的細節是不可能的,并且nginx各個(gè)部分的實(shí)現之間關(guān)系緊密, 不可能做到窺一斑而知全身,合適的做法似乎應該是從main開(kāi)始,先了解nginx的啟動(dòng)過(guò)程的順序,然后進(jìn)行問(wèn)題分解,再逐個(gè)重點(diǎn)分析每一個(gè)重要的部分。

對每個(gè)理解的關(guān)鍵部分進(jìn)行詳細的記錄和整理也是很重要的,這也是這個(gè)源碼分析日志系列所要完成的任務(wù)。

為了更深刻的理解代碼實(shí)現的關(guān)鍵,修改代碼和寫(xiě)一些測試用例是不可避免的,這就需要搭建一個(gè)方便調試的環(huán)境,這也比較容易,因為使用的linux系統本身就是一個(gè)天然的開(kāi)發(fā)調試環(huán)境。

個(gè)人的能力是有限的,幸運的是互聯(lián)網(wǎng)上還有一幫同好也在孜孜不倦的做著(zhù)同樣的事情,與他們的交流會(huì )幫助少走一些彎路,也會(huì )互相促進(jìn),更深入和準確的理解源碼的本實(shí)。

開(kāi)始一次愉快的旅行,go!

源碼分析是一個(gè)逐步取精的過(guò)程,最開(kāi)始是一個(gè)大概了解的過(guò)程,各種認識不會(huì )太深刻,但是把這些真實(shí)的感受也記錄下來(lái),覺(jué)得挺有意思的,可能有些認識是片面或者是不正確的,但可以通過(guò)后面更深入細致的分析過(guò)程,不斷的糾正錯誤和深化理解。源碼分析是一個(gè)過(guò)程,經(jīng)驗是逐步累積起來(lái)的,希望文字可以把這種累積的感覺(jué)也準確記錄下來(lái)。

現在就看看對nginx源碼的第一印象吧。

源碼包解壓之后,根目錄下有幾個(gè)子目錄和幾個(gè)文件,最重要的子目錄是auto和src,最重要的文件是configure腳本,不同于絕大多數的開(kāi)源代碼,nginx的configure腳本是作者手工編寫(xiě)的,沒(méi)有使用autoconf之類(lèi)的工具去自動(dòng)生成,configure腳本會(huì )引用auto目錄下面的腳本文件來(lái)干活。根據不同的用途,auto目錄下面的腳本各司其職,有檢查編譯器版本的,有檢查操作系統版本的,有檢查標準庫版本的,有檢查模塊依賴(lài)情況的,有關(guān)于安裝的,有關(guān)于初始化的,有關(guān)于多線(xiàn)程檢查的等等。configure作為一個(gè)總驅動(dòng),調用這些腳本去生成版本信息頭文件、默認被包含的模塊的聲明代碼和Makefile文件,版本信息頭文件 (ngx_auto_config.h,ngx_auto_headers.h)和默認被包含的模塊的聲明代碼(ngx_modules.c)被放置在新創(chuàng )建的objs目錄下。要注意的是,這幾個(gè)生成的文件和src下面的源代碼一樣重要,對于理解源碼是不可忽略的重要部分。

src是源碼存放的目錄,configure創(chuàng )建的objs/src目錄是用來(lái)存放生成的.o文件的,注意區分一下。

src按照功能特性劃分為幾個(gè)部分,對應著(zhù)是幾個(gè)不同的子目錄。

src/core存放著(zhù)主干部分、基礎數據結構和基礎設施的源碼,main函數在src/core/nginx.c中,這是分析源碼的一個(gè)很好的起點(diǎn)。

src/event存放著(zhù)事件驅動(dòng)模型和相關(guān)模塊的源碼。

src/http存放著(zhù)http server和相關(guān)模塊的源碼。

src/mail存放著(zhù)郵件代理和相關(guān)模塊的源碼。

src/misc存放著(zhù)C++兼容性測試和google perftools模塊的源碼。

src/os存放著(zhù)依賴(lài)于操作系統實(shí)現的源碼,nginx啟動(dòng)過(guò)程中最重要的master和workers創(chuàng )建代碼就在這個(gè)目錄下,多少讓人覺(jué)得有點(diǎn)意外。

nginx 的實(shí)現中有非常多的結構體,一般命名為ngx_XXX_t,這些結構體分散在許多頭文件中,而在src/core/ngx_core.h中把幾乎所有的頭文件都集合起來(lái),所有的實(shí)現文件都會(huì )包含這個(gè)ngx_core.h頭文件,說(shuō)nginx的各部分源碼耦合厲害就是這個(gè)原因,但實(shí)際上nginx各個(gè)部分之間邏輯上是劃分的很清晰的,整體上是一種松散的結構。nginx實(shí)現了一些精巧的基礎數據結構,例如 ngx_string_t,ngx_list_t,ngx_array_t,ngx_pool_t,ngx_buf_t,ngx_queue_t,ngx_rbtree_t,ngx_radix_tree_t 等等,還有一些重要的基礎設施,比如log,configure file,time等等,這些數據結構和基礎設施頻繁的被使用在許多地方,這會(huì )讓人感覺(jué)nginx邏輯上的聯(lián)系比較緊密,但熟悉了這些基礎數據結構的實(shí)現代碼就會(huì )感覺(jué)到這些數據結構都是清晰分明的,并沒(méi)有真正的耦合在一起,只是有些多而已,不過(guò)nginx中“家釀”的代碼也正是它的一個(gè)很明顯的亮點(diǎn)。

nginx是高度模塊化的,可以根據自己的需要定制模塊,也可以自己根據一定的標準開(kāi)發(fā)需要的模塊,已經(jīng)定制的模塊會(huì )在objs/ngx_modules.c中聲明,這個(gè)文件是由configure生成的。

nginx 啟動(dòng)過(guò)程中,很重要的一步就是加載和初始化模塊,這是在ngx_init_cycle中完成的,ngx_init_cycle會(huì )調用模塊的hook接口(init_module)對模塊初始化,ngx_init_cycle還會(huì )調用ngx_open_listening_sockets初始化 socket,如果是多進(jìn)程方式啟動(dòng),就會(huì )調用ngx_master_process_cycle完成最后的啟動(dòng)動(dòng)作,ngx_master_process_cycle調用ngx_start_worker_processes生成多個(gè)工作子進(jìn)程,ngx_start_worker_processes調用ngx_worker_process_cycle創(chuàng )建工作內容,如果進(jìn)程有多個(gè)子線(xiàn)程,這里也會(huì )初始化線(xiàn)程和創(chuàng )建線(xiàn)程工作內容,初始化完成之后,ngx_worker_process_cycle會(huì )進(jìn)入處理循環(huán),調用 ngx_process_events_and_timers,該函數調用ngx_process_events監聽(tīng)事件,并把事件投遞到事件隊列 ngx_posted_events中,最終會(huì )在ngx_event_thread_process_posted中處理事件。

事件機制是nginx中很關(guān)鍵的一個(gè)部分,linux下使用了epool,freebsd下使用了kqueue管理事件。

最后附上Joshua友情提供的源碼大圖一張,感謝:)

nginx的自動(dòng)腳本指的是configure腳本程序和auto子目錄下面的腳本程序。自動(dòng)腳本完成兩件事情,其一是檢查環(huán)境,其二是生成文件。生成的文件有兩類(lèi),一類(lèi)是編譯代碼需要的Makefile文件,一類(lèi)是根據環(huán)境檢查結果生成的c代碼。生成的Makefile很干凈,也很容易閱讀。生成的c代碼有三個(gè)文件,ngx_auto_config.h是根據環(huán)境檢查的結果聲明的一些宏定義,這個(gè)頭文件被include進(jìn)ngx_core.h中,所以會(huì )被所有的源碼引用到,這確保了源碼是可移植的;ngx_auto_headers.h中也是一些宏定義,不過(guò)是關(guān)于系統頭文件存在性的聲明;ngx_modules.c是默認被包含進(jìn)系統中的模塊的聲明,如果想去掉一些模塊,只要修改這個(gè)文件即可。

configure是自動(dòng)腳本的總驅動(dòng),它通過(guò)組合auto目錄下不同功能的腳本程序完成環(huán)境檢查和生成文件的任務(wù)。環(huán)境檢查主要是三個(gè)部分:編譯器版本及支持特性、操作系統版本及支持特性、第三方庫支持,檢查的腳本程序分別存放在auto/cc、auto/os、auto/lib三個(gè)子目錄中。檢查的方法很有趣,通過(guò)自動(dòng)編譯用于檢查某個(gè)特性的代碼片段,根據編譯器的輸出情況判定是否支持該種特性。根據檢查的情況,如果環(huán)境足以支持運行一個(gè)簡(jiǎn)單版本的nginx,就會(huì )生成 Makefile和c代碼,這些文件會(huì )存放在新創(chuàng )建的objs目錄下。當然,也可能會(huì )失敗,假如系統不支持pcre和ssh,如果沒(méi)有屏蔽掉相關(guān)的模塊,自動(dòng)腳本就會(huì )失敗。

auto目錄下的腳本職能劃分非常清晰,有檢查環(huán)境的,有檢查模塊的,有提供幫助信息的(./configure –help),有處理腳本參數的,也有一些腳本純粹是為了模塊化自動(dòng)腳本而設計出來(lái)的,比如feature腳本是用于檢查單一特性的,其他的環(huán)境檢查腳本都會(huì )調用這個(gè)腳本去檢查某個(gè)特性。還有一些腳本是用來(lái)輸出信息到生成文件的,比如have、nohave、make、install等。

之所以要在源碼分析中專(zhuān)門(mén)談及自動(dòng)腳本,是因為nginx的自動(dòng)腳本不是用autoconf之類(lèi)的工具生成的,而是作者手工編寫(xiě)的,并且包含一定的設計成分,對于需要編寫(xiě)自動(dòng)腳本的人來(lái)說(shuō),有很高的參考價(jià)值。這里也僅僅是粗略的介紹一下,需要詳細了解最好是讀一下這些腳本,這些腳本并沒(méi)有使用多少生僻的語(yǔ)法,可讀性是不錯的。

btw,后面開(kāi)始進(jìn)入真正的源碼分析階段,nginx的源碼中有非常多的結構體,這些結構體之間引用也很頻繁,很難用文字表述清楚之間的關(guān)系,覺(jué)得用圖表是最好的方式,因此需要掌握一種高效靈活的作圖方法,我選擇的是graphviz,這是at&t貢獻的跨平臺的圖形生成工具,通過(guò)寫(xiě)一種稱(chēng)為“the dot language”的腳本語(yǔ)言,然后用dot命令就可以直接生成指定格式的圖,很方便。


看了幾天的源碼,進(jìn)度很慢,過(guò)于關(guān)注代碼實(shí)現的細節了,反而很難看清整體結構。于是問(wèn)諸google尋找方法。大體上分析源代碼都要經(jīng)歷三遍過(guò)程,第一遍是瀏覽,通過(guò)閱讀源碼的文檔和注釋?zhuān)喿x接口,先弄清楚每個(gè)模塊是干什么的而不關(guān)心它是怎么做的,畫(huà)出架構草圖;第二遍是精讀,根據架構草圖把系統分為小部分,每個(gè)部分從源碼實(shí)現自底向上的閱讀,更深入細致的理解每個(gè)模塊的實(shí)現方式以及與模塊外部的接口方式等,弄明白模塊是怎么做的,為什么這樣做,有沒(méi)有更好的方式,自己會(huì )如何實(shí)現等等問(wèn)題;第三遍是總結回顧,完善架構圖,把架構圖中那些模糊的或者空著(zhù)的模塊重新補充完善,把一些可復用的實(shí)現放入自己的代碼庫中。

現在是瀏覽階段,并不適合過(guò)早涉及代碼的實(shí)現細節,要借助nginx的文檔理解其整體架構和模塊劃分。經(jīng)過(guò)幾年的發(fā)展,nginx現在的文檔已經(jīng)是很豐富了,nginx的英文wiki上包含了各個(gè)模塊的詳細文檔,faq也涵蓋了很豐富的論題,利用這些文檔足以建立 nginx的架構草圖。所以瀏覽階段主要的工作就是閱讀文檔和畫(huà)架構草圖了。

對于源碼分析,工具是相當關(guān)鍵的。這幾天閱讀源碼的過(guò)程,熟悉了三個(gè)殺手級的工具:scrapbook離線(xiàn)文件管理小程序、graphviz圖形生成工具、leo-editor文學(xué)編程理念的編輯器。

scrapbook是firefox下一款輕量高效的離線(xiàn)文件管理擴展程序,利用scrapbook把nginx的wiki站點(diǎn)鏡像到本地只需要幾分鐘而已,管理也相當簡(jiǎn)單,和書(shū)簽類(lèi)似。

graphviz是通過(guò)編程畫(huà)圖的工具集合,用程序把圖形的邏輯和表現表示出來(lái),然后通過(guò)命令行執行適當的命令就可以解析生成圖形。

leo- editor與其說(shuō)是一個(gè)工具平臺,不如說(shuō)是一套理念。和其他編輯器ide不同的是,leo關(guān)注的是文章內容的內在邏輯和段落層次,文章的表現形式和格式是次要的。用leo的過(guò)程,其實(shí)就是在編程,雖然剛開(kāi)始有些不適應,但習慣之后確實(shí)很爽,殺手級的體驗感,很聽(tīng)話(huà)。

btw,充實(shí)的8天長(cháng)假結束了,總結一下,這個(gè)假期沒(méi)有出去玩,做了三件事情,第一件事情是基本完成了房子的裝修,墻面、地板、家具,都是親力親為的成果,雖然沒(méi)有節省多少開(kāi)支,卻增添了動(dòng)手的樂(lè )趣;第二件事情是溜冰,體會(huì )了平衡的力量;第三件事情是練習畫(huà)畫(huà),鍛煉了觀(guān)察與概括的能力。


利用nginx wiki和互聯(lián)網(wǎng)收集了不少nginx相關(guān)的文檔資料,但是仔細閱讀之后發(fā)覺(jué)對理解nginx架構有直接幫助的資料不多,一些有幫助的資料也要結合閱讀部分源碼細節才能搞清楚所述其是,可能nginx在非俄國之外的環(huán)境下流行不久,應用還很簡(jiǎn)單,相關(guān)的英文和中文文檔也就不夠豐富的原因吧。

不過(guò)還是有一些金子的。

如果要了解nginx的概況和使用方法,wiki足以滿(mǎn)足需要,wiki上有各個(gè)模塊的概要和詳細指令說(shuō)明,也有豐富的配置文件示例,不過(guò)對于了解nginx系統架構和開(kāi)發(fā)沒(méi)有相關(guān)的文檔資料。

nginx的開(kāi)發(fā)主要是指撰寫(xiě)自定義模塊代碼。這需要了解nginx的模塊化設計思想,模塊化也是nginx的一個(gè)重要思想,如果要整體上了解nginx,從模塊化入手是一個(gè)不錯的起點(diǎn)。emiller的nginx模塊開(kāi)發(fā)指引是目前最好的相關(guān)資料了(http://emiller.info/nginx-modules-guide.html),這份文檔作為nginx的入門(mén)文檔也是合適的,不過(guò)其中有些內容很晦澀,很難理解,要結合閱讀源碼,反復比對才能真正理解其內涵。

如果要從整體上了解nginx架構和源碼結構,Joshua zhu的廣州技術(shù)沙龍講座的pdf和那張大圖是不錯的材料,這份pdf可以在wiki的資源頁(yè)面中找到地址鏈接。Joshua也給了我一些建議和指引,使我少走了不少彎路,很快進(jìn)入狀態(tài),感謝。

相信最好的文檔就是源碼本身了。隨著(zhù)閱讀源碼的量越來(lái)越大,也越來(lái)越深入,使我認識到最寶貴的文檔就在源碼本身,之前提到過(guò),nginx的代碼質(zhì)量很高,命名比較講究,雖然很少注釋?zhuān)呛苡袟l理的結構體命名和變量命名使得閱讀源碼就像是閱讀文檔。不過(guò)要想順利的保持這種感覺(jué)也不是一件簡(jiǎn)單的事情,覺(jué)得要做好如下幾點(diǎn):

1)熟悉C語(yǔ)言,尤其是對函數指針和宏定義要有足夠深入的理解,nginx是模塊化的,它的模塊化不同于apache,它不是動(dòng)態(tài)加載模塊,而是把需要的模塊都編譯到系統中,這些模塊可以充分利用系統核心提供的諸多高效的組件,把數據拷貝降到最低的水平,所以這些模塊的實(shí)現利用了大量的函數指針實(shí)現回掉操作,幾乎是無(wú)函數指針不nginx的地步。

2)重點(diǎn)關(guān)注nginx的命名,包括函數命名,結構體命名和變量命名,這些命名把nginx看似耦合緊密的實(shí)現代碼清晰的分開(kāi)為不同層次不同部分的組件和模塊,這等效于注釋。尤其要關(guān)注變量的命名,后面關(guān)于模塊的分析中會(huì )再次重申這一點(diǎn)。

3)寫(xiě)一個(gè)自定義的模塊,利用nginx強大的內部組件,這是深入理解nginx的一個(gè)有效手段。

接下來(lái)的分析過(guò)程,著(zhù)眼于兩個(gè)重點(diǎn),一個(gè)就是上面提到的模塊化思想的剖析,力爭結合自身理解把這個(gè)部分分析透徹;另一個(gè)重點(diǎn)是nginx的事件處理流程,這是高性能的核心,是nginx的core。

go on


源碼的src/core目錄下實(shí)現了不少精巧的數據結構,最重要的有:內存池ngx_pool_t、緩沖區ngx_buf_t、緩沖區鏈 ngx_chain_t、字符串ngx_str_t、數組ngx_array_t、鏈表ngx_list_t、隊列ngx_queue_t、基于hash 的關(guān)聯(lián)數組ngx_hash_t、紅黑樹(shù)ngx_rbtree_t、radix樹(shù)ngx_radix_tree_t等,這些數據結構頻繁出現在源碼中,而且這些結構之間也是彼此配合,聯(lián)系緊密,很難孤立某一個(gè)出來(lái)。每個(gè)數據結構都提供了一批相關(guān)的操作接口,一般設置數據的接口(add)只是從內存池中分配空間并返回指向結構體的指針,然后利用這個(gè)指針再去設置成員,這使得此類(lèi)接口的實(shí)現保持干凈簡(jiǎn)潔。這些數據結構的實(shí)現分析是有一定難度的,不過(guò)我覺(jué)得不了解實(shí)現也并不影響理解整體的架構,只要知道有這么些重要的數據結構和基本的功用就足以滿(mǎn)足需要了,我會(huì )在搞清楚整體架構之后再詳細分析一下這些精致的玩意。

nginx的模塊化架構和實(shí)現方式是源碼分析系列中最關(guān)鍵的一個(gè)部分。模塊化是nginx的骨架,事件處理是nginx的心臟,而每個(gè)具體模塊構成了nginx的血肉,摸清骨架,才能游刃有余。

先高屋建瓴的快速瀏覽一下nginx的模塊化架構,后面再詳細分析每一個(gè)重要數據結構和實(shí)現方式。

nginx 的模塊在源碼中對應著(zhù)是ngx_module_t結構的變量,有一個(gè)全局的ngx_module_t指針數組,這個(gè)指針數組包含了當前編譯版本支持的所有模塊,這個(gè)指針數組的定義是在自動(dòng)腳本生成的objs/ngx_modules.c文件中,下面是在我的機器上編譯的nginx版本(0.8.9)的模塊聲明:

extern ngx_module_t ngx_core_module;
extern ngx_module_t ngx_errlog_module;
extern ngx_module_t ngx_conf_module;

extern ngx_module_t ngx_events_module;
extern ngx_module_t ngx_event_core_module;
extern ngx_module_t ngx_epoll_module;

extern ngx_module_t ngx_http_module;
extern ngx_module_t ngx_http_core_module;
extern ngx_module_t ngx_http_log_module;
extern ngx_module_t ngx_http_upstream_module;
extern ngx_module_t ngx_http_static_module;
extern ngx_module_t ngx_http_autoindex_module;
extern ngx_module_t ngx_http_index_module;
extern ngx_module_t ngx_http_auth_basic_module;
extern ngx_module_t ngx_http_access_module;
extern ngx_module_t ngx_http_limit_zone_module;
extern ngx_module_t ngx_http_limit_req_module;
extern ngx_module_t ngx_http_geo_module;
extern ngx_module_t ngx_http_map_module;
extern ngx_module_t ngx_http_referer_module;
extern ngx_module_t ngx_http_rewrite_module;
extern ngx_module_t ngx_http_proxy_module;
extern ngx_module_t ngx_http_fastcgi_module;
extern ngx_module_t ngx_http_memcached_module;
extern ngx_module_t ngx_http_empty_gif_module;
extern ngx_module_t ngx_http_browser_module;
extern ngx_module_t ngx_http_upstream_ip_hash_module;
extern ngx_module_t ngx_http_write_filter_module;
extern ngx_module_t ngx_http_header_filter_module;
extern ngx_module_t ngx_http_chunked_filter_module;
extern ngx_module_t ngx_http_range_header_filter_module;
extern ngx_module_t ngx_http_gzip_filter_module;
extern ngx_module_t ngx_http_postpone_filter_module;
extern ngx_module_t ngx_http_charset_filter_module;
extern ngx_module_t ngx_http_ssi_filter_module;
extern ngx_module_t ngx_http_userid_filter_module;
extern ngx_module_t ngx_http_headers_filter_module;
extern ngx_module_t ngx_http_copy_filter_module;
extern ngx_module_t ngx_http_range_body_filter_module;
extern ngx_module_t ngx_http_not_modified_filter_module;

ngx_module_t *ngx_modules[] = {
&ngx_core_module,
&ngx_errlog_module,
&ngx_conf_module,
&ngx_events_module,
&ngx_event_core_module,
&ngx_epoll_module,
&ngx_http_module,
&ngx_http_core_module,
&ngx_http_log_module,
&ngx_http_upstream_module,
&ngx_http_static_module,
&ngx_http_autoindex_module,
&ngx_http_index_module,
&ngx_http_auth_basic_module,
&ngx_http_access_module,
&ngx_http_limit_zone_module,
&ngx_http_limit_req_module,
&ngx_http_geo_module,
&ngx_http_map_module,
&ngx_http_referer_module,
&ngx_http_rewrite_module,
&ngx_http_proxy_module,
&ngx_http_fastcgi_module,
&ngx_http_memcached_module,
&ngx_http_empty_gif_module,
&ngx_http_browser_module,
&ngx_http_upstream_ip_hash_module,
&ngx_http_write_filter_module,
&ngx_http_header_filter_module,
&ngx_http_chunked_filter_module,
&ngx_http_range_header_filter_module,
&ngx_http_gzip_filter_module,
&ngx_http_postpone_filter_module,
&ngx_http_charset_filter_module,
&ngx_http_ssi_filter_module,
&ngx_http_userid_filter_module,
&ngx_http_headers_filter_module,
&ngx_http_copy_filter_module,
&ngx_http_range_body_filter_module,
&ngx_http_not_modified_filter_module,
NULL
};

這里只有每個(gè)模塊變量的聲明(注意extern聲明符),而每個(gè)模塊變量的定義包含在各自模塊實(shí)現的文件中,比如ngx_core_module定義在src/core/nginx.c中:

ngx_module_t ngx_core_module = {
NGX_MODULE_V1,
&ngx_core_module_ctx, /* module context */
ngx_core_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
};

提問(wèn):這么多的模塊,在nginx中究竟是如何使用的呢,模塊的功能是如何體現的呢?

這就要先說(shuō)一下nginx啟動(dòng)的過(guò)程了,nginx是一個(gè)master主進(jìn)程+多個(gè)worker子進(jìn)程的工作模式 (詳細會(huì )在分析事件處理的時(shí)候解釋),nginx主進(jìn)程啟動(dòng)的過(guò)程中會(huì )按照初始化master、初始化模塊、初始化工作進(jìn)程、(初始化線(xiàn)程、退出線(xiàn)程)、退出工作進(jìn)程、退出master順序進(jìn)行,而在這些子過(guò)程內部和子過(guò)程之間,又會(huì )有讀取配置、創(chuàng )建配置、初始化配置、合并配置、http解析、http過(guò)濾、http輸出、http代理等過(guò)程,在這些過(guò)程開(kāi)始前后、過(guò)程中、結束前后等時(shí)機,nginx調用合適的模塊接口完成特定的任務(wù)。

所謂的合適模塊接口,是各個(gè)模塊通過(guò)一些方式注冊到系統內的回調函數,這些回調函數都要符合一定的接口規范,比如上面那個(gè)ngx_core_module變量的定義初始化中,那些賦值為NULL的都是函數指針,如果要注冊一個(gè)回調函數,就可以按照函數指針指定的接口編寫(xiě)函數,然后通過(guò)賦值給這些函數指針來(lái)注冊回調函數,不同的接口被nginx調用的時(shí)機是不同的,比如init_master是在初始化master的時(shí)候被調用。

到這里,應該對nginx的模塊化架構有了一些感性認識了,接下來(lái)就進(jìn)入具體實(shí)現方式的分析,會(huì )稍微復雜一些,最好能夠結合源碼加深一下理解。

go on

分析nginx的模塊化架構的實(shí)現方式,就要從ngx_module_t結構體入手。ngx_module_t的聲明在src/core/ngx_conf_file.h中:

#define NGX_MODULE_V1 0, 0, 0, 0, 0, 0, 1
#define NGX_MODULE_V1_PADDING 0, 0, 0, 0, 0, 0, 0, 0

struct ngx_module_s {
ngx_uint_t ctx_index;
ngx_uint_t index;

ngx_uint_t spare0;
ngx_uint_t spare1;
ngx_uint_t spare2;
ngx_uint_t spare3;

ngx_uint_t version;

void *ctx;
ngx_command_t *commands;
ngx_uint_t type;

ngx_int_t (*init_master)(ngx_log_t *log);

ngx_int_t (*init_module)(ngx_cycle_t *cycle);

ngx_int_t (*init_process)(ngx_cycle_t *cycle);
ngx_int_t (*init_thread)(ngx_cycle_t *cycle);
void (*exit_thread)(ngx_cycle_t *cycle);
void (*exit_process)(ngx_cycle_t *cycle);

void (*exit_master)(ngx_cycle_t *cycle);

uintptr_t spare_hook0;
uintptr_t spare_hook1;
uintptr_t spare_hook2;
uintptr_t spare_hook3;
uintptr_t spare_hook4;
uintptr_t spare_hook5;
uintptr_t spare_hook6;
uintptr_t spare_hook7;
};

index是一個(gè)模塊計數器,按照每個(gè)模塊在ngx_modules[]數組中的聲明順序(見(jiàn)objs/ngx_modules.c),從0開(kāi)始依次給每個(gè)模塊進(jìn)行編號:

ngx_max_module = 0;
for (i = 0; ngx_modules[i]; i++) {
ngx_modules[i]->index = ngx_max_module++;
}(見(jiàn)src/core/nginx.c)

ctx_index是分類(lèi)的模塊計數器,nginx的模塊可以分為四種:core、event、http和mail,每一種的模塊又會(huì )各自計數一下,這個(gè)ctx_index就是每個(gè)模塊在其所屬類(lèi)組的計數值:

ngx_event_max_module = 0;
for (i = 0; ngx_modules[i]; i++) {
if (ngx_modules[i]->type != NGX_EVENT_MODULE) {
continue;
}

ngx_modules[i]->ctx_index = ngx_event_max_module++;
}(見(jiàn)src/event/ngx_event.c)

ngx_http_max_module = 0;
for (m = 0; ngx_modules[m]; m++) {
if (ngx_modules[m]->type != NGX_HTTP_MODULE) {
continue;
}

ngx_modules[m]->ctx_index = ngx_http_max_module++;
}(見(jiàn)src/http/ngx_http.c)

ngx_mail_max_module = 0;
for (m = 0; ngx_modules[m]; m++) {
if (ngx_modules[m]->type != NGX_MAIL_MODULE) {
continue;
}

ngx_modules[m]->ctx_index = ngx_mail_max_module++;
}(見(jiàn)src/mail/ngx_mail.c)

ctx是模塊的上下文,不同種類(lèi)的模塊有不同的上下文,四類(lèi)模塊就有四種模塊上下文,實(shí)現為四個(gè)不同的結構體,所以ctx是void *。這是一個(gè)很重要的字段,后面會(huì )詳細剖析。

commands 是模塊的指令集,nginx的每個(gè)模塊都可以實(shí)現一些自定義的指令,這些指令寫(xiě)在配置文件的適當配置項中,每一個(gè)指令在源碼中對應著(zhù)一個(gè) ngx_command_t結構的變量,nginx會(huì )從配置文件中把模塊的指令讀取出來(lái)放到模塊的commands指令數組中,這些指令一般是把配置項的參數值賦給一些程序中的變量或者是在不同的變量之間合并或轉換數據(例如include指令),指令可以帶參數也可以不帶參數,你可以把這些指令想象為 unix的命令行或者是一種模板語(yǔ)言的指令。在nginx的wiki上有每個(gè)系統內置模塊的指令說(shuō)明。指令集會(huì )在下一篇中詳細剖析。

type就是模塊的種類(lèi),前面已經(jīng)說(shuō)過(guò),nginx模塊分為core、event、http和mail四類(lèi),type用宏定義標識四個(gè)分類(lèi)。

init_master、 init_module、init_process、init_thread、exit_thread、exit_process、 exit_master是函數指針,指向模塊實(shí)現的自定義回調函數,這些回調函數分別在初始化master、初始化模塊、初始化工作進(jìn)程、初始化線(xiàn)程、退出線(xiàn)程、退出工作進(jìn)程和退出master的時(shí)候被調用,如果模塊需要在這些時(shí)機做處理,就可以實(shí)現對應的函數,并把它賦值給對應的函數指針來(lái)注冊一個(gè)回調函數接口。

其余的參數沒(méi)研究過(guò),貌似從來(lái)沒(méi)有用過(guò),在定義模塊的時(shí)候,最前面的7個(gè)字段和最后面的8個(gè)字段的初始化是使用宏NGX_MODULE_V1和NGX_MODULE_V1_PADDING完成的,例如:

ngx_module_t ngx_core_module = {
NGX_MODULE_V1,
&ngx_core_module_ctx, /* module context */
ngx_core_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
};(見(jiàn)src/core/nginx.c)

接下來(lái)剖析一下ngx_module_t的ctx成員,這個(gè)成員的意義是每個(gè)模塊的上下文,所謂的上下文,也就是這個(gè)模塊究竟可以做什么,從前面的分析可以知道nginx把所有模塊分為四類(lèi)(core/event/http/mail),對應的,nginx也認為模塊的上下文是四種,分別用四個(gè)結構體表示:ngx_core_module_t、ngx_event_module_t、ngx_http_module_t、 ngx_mail_module_t。也就是說(shuō),如果一個(gè)模塊屬于core分類(lèi),那么其上下文就是ngx_core_module_t結構的變量,其他類(lèi)推。這四個(gè)結構體類(lèi)似于ngx_module_t,也是一些函數指針的集合,每個(gè)模塊根據自己所屬的分類(lèi),自定義一些操作函數,通過(guò)把這些操作函數賦值為對應分類(lèi)結構體中的函數指針,這就注冊了一個(gè)回調函數接口,從而就可以實(shí)現更細致的功能了,例如可以為event模塊添加事件處理函數,可以為http模塊添加過(guò)濾函數等。

這四個(gè)結構體和commands會(huì )在下一篇中詳細剖析,現在把注意力轉移一下,來(lái)品味一下nginx的命名,找出其規律,這對閱讀源碼的幫助是非常大的。

首先是模塊,模塊是ngx_module_t結構的變量,其命名格式為:ngx__module。
然后是模塊上下文,模塊上下文根據不同的模塊分類(lèi)分別是ngx_core_module_t、ngx_event_module_t、 ngx_http_module_t和ngx_mail_module_t結構的變量,其命名格式為:ngx__module_ctx。
接著(zhù)是模塊命令集,模塊命令集是ngx_command_t的指針數組,其命名格式為:ngx__commands。

go on

接下來(lái)剖析模塊的指令。模塊的指令在源碼中是ngx_command_t結構的變量,ngx_command_t的聲明在src/core/ngx_conf_file.h中:

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;
};

name是指令名稱(chēng)的字符串,不包含空格。

type是標識符集,標識指令在配置文件中的合法位置和指令的參數個(gè)數。這是一個(gè)至少有32bit的無(wú)符號整形,前16bit用于標識位置,后16bit用于標識參數。

先看看參數的標識,宏定義在src/core/ngx_conf_file.h中:

#define NGX_CONF_NOARGS 0×00000001 (沒(méi)有參數)
#define NGX_CONF_TAKE1 0×00000002 (有1個(gè)參數)
#define NGX_CONF_TAKE2 0×00000004 (有2個(gè)參數)
#define NGX_CONF_TAKE3 0×00000008 (有3個(gè)參數)
#define NGX_CONF_TAKE4 0×00000010 (有4個(gè)參數)
#define NGX_CONF_TAKE5 0×00000020 (有5個(gè)參數)
#define NGX_CONF_TAKE6 0×00000040 (有6個(gè)參數)
#define NGX_CONF_TAKE7 0×00000080 (有7個(gè)參數)

#define NGX_CONF_MAX_ARGS 8

#define NGX_CONF_TAKE12 (NGX_CONF_TAKE1|NGX_CONF_TAKE2) (有1個(gè)或者有2個(gè)參數)
#define NGX_CONF_TAKE13 (NGX_CONF_TAKE1|NGX_CONF_TAKE3)

#define NGX_CONF_TAKE23 (NGX_CONF_TAKE2|NGX_CONF_TAKE3)

#define NGX_CONF_TAKE123 (NGX_CONF_TAKE1|NGX_CONF_TAKE2|NGX_CONF_TAKE3)
#define NGX_CONF_TAKE1234 (NGX_CONF_TAKE1|NGX_CONF_TAKE2|NGX_CONF_TAKE3 \
|NGX_CONF_TAKE4)

#define NGX_CONF_ARGS_NUMBER 0×000000ff
#define NGX_CONF_BLOCK 0×00000100
#define NGX_CONF_FLAG 0×00000200 (有一個(gè)布爾型參數)
#define NGX_CONF_ANY 0×00000400
#define NGX_CONF_1MORE 0×00000800 (至多有1個(gè)參數)
#define NGX_CONF_2MORE 0×00001000 (至多有2個(gè)參數)
#define NGX_CONF_MULTI 0×00002000

再看看位置的標識,位置的標識宏定義在幾個(gè)文件中:

src/core/ngx_conf_file.h:
#define NGX_DIRECT_CONF 0×00010000

#define NGX_MAIN_CONF 0×01000000
#define NGX_ANY_CONF 0×0F000000

src/event/ngx_event.h:
#define NGX_EVENT_CONF 0×02000000

src/http/ngx_http_config.h:
#define NGX_HTTP_MAIN_CONF 0×02000000
#define NGX_HTTP_SRV_CONF 0×04000000
#define NGX_HTTP_LOC_CONF 0×08000000
#define NGX_HTTP_UPS_CONF 0×10000000
#define NGX_HTTP_SIF_CONF 0×20000000
#define NGX_HTTP_LIF_CONF 0×40000000
#define NGX_HTTP_LMT_CONF 0×80000000

src/mail/ngx_mail.h:
#define NGX_MAIL_MAIN_CONF 0×02000000
#define NGX_MAIL_SRV_CONF 0×04000000

要理解上面所謂的合法位置的真正含義,就要了解一下nginx的配置文件了,這里就不累述了,不影響下面的分析,我會(huì )在很后面的時(shí)候分析一下nginx的配置文件,因為那是一個(gè)big topic。

set 是一個(gè)函數指針,這個(gè)函數主要是從配置文件中把該指令的參數(存放在ngx_conf_t中)轉換為合適的數據類(lèi)型并將轉換后的值保存到模塊的配置結構體中(void *conf),這個(gè)配置結構體又是用void *指向的,應該可以料到這說(shuō)明每個(gè)模塊的配置結構體是不同的,這些結構體命名格式為:ngx__conf_t,至于要把轉換后的值放到配置結構體的什么位置,就要依靠offset了,offset是調用了offsetof函數計算出的結構體中某個(gè)成員的偏移位置。

并不是所有的模塊都要定義一個(gè)配置結構體,因為set也可能是一個(gè)簡(jiǎn)單的操作函數,它可能只是從配置中(ngx_conf_t)讀取一些數據進(jìn)行簡(jiǎn)單的操作,比如errlog模塊的“error_log”指令就是調用ngx_error_log寫(xiě)一條日志,并不需要存儲什么配置數據。

conf和offset,offset前面已經(jīng)提到,它是配置結構體中成員的偏移。conf也是一個(gè)偏移值,不過(guò)它是配置文件結構體的(ngx_conf_t)成員ctx的成員的偏移,一般是用來(lái)把ctx中指定偏移位置的成員賦值給void *conf。

post指向模塊讀配置的時(shí)候需要的一些零碎變量。

從上面的分析可以看出,每個(gè)模塊會(huì )映射到配置文件中的某個(gè)位置,全局位置的配置會(huì )被下一級的配置繼承,比如http_main會(huì )被http_svr繼承,http_svr會(huì )被http_loc繼承,這些繼承在源碼中是調用模塊上下文的合并配置的接口完成的。

ngx_command_t的set成員也可以作為一個(gè)回調函數,通過(guò)把自定義的操作函數賦值給set來(lái)注冊一些操作。

到目前為止,已經(jīng)了解不少回調函數了,這些回調函數用來(lái)注冊模塊的自定義操作,我有時(shí)稱(chēng)它為接口,有時(shí)稱(chēng)它為回調函數,有點(diǎn)混亂,接下來(lái)的分析文章中,進(jìn)行一下統一,全部稱(chēng)為鉤子(hook)。把現在已經(jīng)分析過(guò)的鉤子羅列一下:

ngx_module_t -> init_master
ngx_module_t -> init_module
ngx_module_t -> init_process
ngx_module_t -> init_thread
ngx_module_t -> exit_thread
ngx_module_t -> exit_process
ngx_module_t -> exit_master

ngx_command_t -> set

下一篇剖析模塊上下文的時(shí)候,會(huì )有更多的鉤子,這就是為什么要對c語(yǔ)言的指針深入理解的原因了,nginx中到處都是鉤子,假如要自己寫(xiě)一個(gè)模塊,可以通過(guò)這些鉤子把自己的模塊掛到nginx的處理流中,參與到nginx運行的每個(gè)特定階段,當然,也不是隨意的嵌入,要精確定義模塊何時(shí)如何產(chǎn)生作用才是有意義的,這不是一件輕松的事情。

go on

模塊的上下文是四個(gè)結構體定義的:ngx_core_module_t、ngx_event_module_t、ngx_http_module_t、ngx_mail_module_t,分別對應于四類(lèi)模塊。

typedef struct {
ngx_str_t name;
void *(*create_conf)(ngx_cycle_t *cycle);
char *(*init_conf)(ngx_cycle_t *cycle, void *conf);
} ngx_core_module_t;(見(jiàn)src/core/ngx_conf_file.h)

typedef struct {
ngx_int_t (*add)(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags);
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);

ngx_int_t (*add_conn)(ngx_connection_t *c);
ngx_int_t (*del_conn)(ngx_connection_t *c, ngx_uint_t flags);

ngx_int_t (*process_changes)(ngx_cycle_t *cycle, ngx_uint_t nowait);
ngx_int_t (*process_events)(ngx_cycle_t *cycle, ngx_msec_t timer,
ngx_uint_t flags);

ngx_int_t (*init)(ngx_cycle_t *cycle, ngx_msec_t timer);
void (*done)(ngx_cycle_t *cycle);
} ngx_event_actions_t;

typedef struct {
ngx_str_t *name;

void *(*create_conf)(ngx_cycle_t *cycle);
char *(*init_conf)(ngx_cycle_t *cycle, void *conf);

ngx_event_actions_t actions;
} ngx_event_module_t;(見(jiàn)src/event/ngx_event.h)

typedef struct {
ngx_int_t (*preconfiguration)(ngx_conf_t *cf);
ngx_int_t (*postconfiguration)(ngx_conf_t *cf);

void *(*create_main_conf)(ngx_conf_t *cf);
char *(*init_main_conf)(ngx_conf_t *cf, void *conf);

void *(*create_srv_conf)(ngx_conf_t *cf);
char *(*merge_srv_conf)(ngx_conf_t *cf, void *prev, void *conf);

void *(*create_loc_conf)(ngx_conf_t *cf);
char *(*merge_loc_conf)(ngx_conf_t *cf, void *prev, void *conf);
} ngx_http_module_t;(見(jiàn)src/http/ngx_http_config.h)

typedef struct {
ngx_mail_protocol_t *protocol;

void *(*create_main_conf)(ngx_conf_t *cf);
char *(*init_main_conf)(ngx_conf_t *cf, void *conf);

void *(*create_srv_conf)(ngx_conf_t *cf);
char *(*merge_srv_conf)(ngx_conf_t *cf, void *prev,
void *conf);
} ngx_mail_module_t;(見(jiàn)src/mail/ngx_mail.h)

這四個(gè)結構體都提供了鉤子供模塊注冊與上下文有關(guān)的操作,核心模塊提供了create_conf和init_conf兩個(gè)鉤子;event模塊也提供了 create_conf和init_conf兩個(gè)鉤子,除此之外還提供了一集操作事件的鉤子;http模塊提供了幾個(gè)在讀取配置文件前后和操作 mail/srv/loc配置項時(shí)候執行的鉤子;mail模塊也提供一些類(lèi)似于http模塊的鉤子,不過(guò)比http模塊簡(jiǎn)單一些。

只要設置了鉤子,這些鉤子就會(huì )在特定的時(shí)機被正確調用,只不過(guò)模塊的種類(lèi)不同,其鉤子調用的時(shí)機也就大不同,這是由于nginx的四種模塊之間的差別很大:核心類(lèi)模塊一般是全局性的模塊,它們會(huì )在系統的許多部分被使用,比如errlog模塊負責寫(xiě)錯誤日志,它被使用在許多地方;event類(lèi)模塊是事件驅動(dòng)相關(guān)的模塊,nginx在不同操作系統上都會(huì )有結合該操作系統特有接口的事件處理模塊,比如在linux中可以使用epoll模塊,在freebsd中可以使用 kqueue模塊,在solaris中可以使用devpoll事件等;http類(lèi)模塊是用于處理http輸入,產(chǎn)生輸出,過(guò)濾輸出,負載均衡等的模塊,這是nginx作為web服務(wù)器的核心部分,Emiller的論文就是關(guān)于http模塊的開(kāi)發(fā)指引;mail類(lèi)模塊是實(shí)現郵件代理的模塊,實(shí)現了 smtp/pop3/imap等協(xié)議的郵件代理。

到這里,已經(jīng)把nginx拆開(kāi)為一個(gè)個(gè)的模塊,后面的分析就可以對每個(gè)模塊進(jìn)行詳細的剖析,也就可以進(jìn)入閱讀源碼的精讀階段了。在這之前,再?lài)L試把這些獨立的模塊串一下,從main開(kāi)始,看看nginx是何時(shí)在何地調度這些模塊干活的。

main
{

// 所有模塊點(diǎn)一下數
ngx_max_module = 0;
for (i = 0; ngx_modules[i]; i++) {
ngx_modules[i]->index = ngx_max_module++;
}

// 主要的初始化過(guò)程,全局的數據存放到ngx_cycle_t結構的變量中
cycle = ngx_init_cycle(&init_cycle);

// 啟動(dòng)進(jìn)程干活
if (ngx_process == NGX_PROCESS_SINGLE) {
ngx_single_process_cycle(cycle);

} else {
ngx_master_process_cycle(cycle);
}

return 0;
}

ngx_init_cycle
{

// 調度core模塊的鉤子create_conf,并且把創(chuàng )建的配置結構體變量存放到cyclep->conf_ctx中
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_conf_parse

// 調度core模塊的鉤子init_conf,設置剛才創(chuàng )建的配置結構體變量(用從配置文件中讀取的數據賦值)
for (i = 0; ngx_modules[i]; i++) {
if (ngx_modules[i]->type != NGX_CORE_MODULE) {
continue;
}

module = ngx_modules[i]->ctx;

if (module->init_conf) {
if (module->init_conf(cycle, cycle->conf_ctx[ngx_modules[i]->index])
== NGX_CONF_ERROR)
{
ngx_destroy_cycle_pools(&conf);
return NULL;
}
}
}

// 調度所有模塊的init_module鉤子,初始化模塊
for (i = 0; ngx_modules[i]; i++) {
if (ngx_modules[i]->init_module) {
if (ngx_modules[i]->init_module(cycle) != NGX_OK) {
/* fatal */
exit(1);
}
}
}


}

ngx_conf_parse
{

ngx_conf_handler

}

ngx_conf_handler
{

// 處理模塊的指令集
for (i = 0; ngx_modules[i]; i++) {

/* look up the directive in the appropriate modules */

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;
}

/* is the directive’s location right ? */

if (!(cmd->type & cf->cmd_type)) {
if (cmd->type & NGX_CONF_MULTI) {
multi = 1;
continue;
}

goto not_allowed;
}

if (!(cmd->type & NGX_CONF_BLOCK) && last != NGX_OK) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
“directive \”%s\” is not terminated by \”;\”",
name->data);
return NGX_ERROR;
}

if ((cmd->type & NGX_CONF_BLOCK) && last != NGX_CONF_BLOCK_START) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
“directive \”%s\” has no opening \”{\”",
name->data);
return NGX_ERROR;
}

/* is the directive’s argument count right ? */

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) {

if (cf->args->nelts < 2) {
goto invalid;
}

} else if (cmd->type & NGX_CONF_2MORE) {

if (cf->args->nelts < 3) {
goto invalid;
}

} else if (cf->args->nelts > NGX_CONF_MAX_ARGS) {

goto invalid;

} else if (!(cmd->type & argument_number[cf->args->nelts - 1]))
{
goto invalid;
}
}

/* set up the directive’s configuration context */

conf = NULL;

if (cmd->type & NGX_DIRECT_CONF) {
conf = ((void **) cf->ctx)[ngx_modules[i]->index];

} else if (cmd->type & NGX_MAIN_CONF) {
conf = &(((void **) cf->ctx)[ngx_modules[i]->index]);

} else if (cf->ctx) {
confp = *(void **) ((char *) cf->ctx + cmd->conf);

if (confp) {
conf = confp[ngx_modules[i]->ctx_index];
}
}

// 調度指令的鉤子set
rv = cmd->set(cf, cmd, conf);

if (rv == NGX_CONF_OK) {
return NGX_OK;
}

if (rv == NGX_CONF_ERROR) {
return NGX_ERROR;
}

ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
“\”%s\” directive %s”, name->data, rv);

return NGX_ERROR;
}
}


}

ngx_master_process_cycle
{

ngx_start_worker_processes

ngx_master_process_exit

}

ngx_start_worker_processes
{

// for i = 0 to n
ngx_worker_process_cycle


}

ngx_worker_process_cycle
{

ngx_worker_process_init

ngx_worker_process_exit

}

ngx_worker_process_init
{

// 調度所有模塊的鉤子init_process
for (i = 0; ngx_modules[i]; i++) {
if (ngx_modules[i]->init_process) {
if (ngx_modules[i]->init_process(cycle) == NGX_ERROR) {
/* fatal */
exit(2);
}
}
}


}

ngx_worker_process_exit
{

// 調度所有模塊的鉤子exit_process
for (i = 0; ngx_modules[i]; i++) {
if (ngx_modules[i]->exit_process) {
ngx_modules[i]->exit_process(cycle);
}
}


}

ngx_master_process_exit
{

// 調度所有模塊的鉤子exit_master
for (i = 0; ngx_modules[i]; i++) {
if (ngx_modules[i]->exit_master) {
ngx_modules[i]->exit_master(cycle);
}
}


}

在這個(gè)調度流中,我們并沒(méi)有看到event類(lèi)模塊、http類(lèi)模塊和mail類(lèi)模塊鉤子的調度,那是由于這三類(lèi)模塊注冊到ngx_module_t的鉤子和ngx_command_t的鉤子上的操作中調度了這些獨特的模塊上下文的鉤子。


nginx有兩個(gè)重要頭文件:ngx_config.h和ngx_core.h。

src/core/ngx_config.h文件中包含的是和操作系統相關(guān)的宏定義和頭文件,其中又會(huì )包含objs/ngx_auto_headers.h和src/os/unix/ngx__config.h,前面提到過(guò),這個(gè)頭文件是自動(dòng)腳本檢驗操作系統后生成的,這個(gè)頭文件中包含了一些宏定義,這些宏定義說(shuō)明了存在哪些與特定操作系統有關(guān)的頭文件,并依此判斷出操作系統的種類(lèi)(linux、freebsd、solaris、darwin等),根據判斷出的操作系統種類(lèi),ngx_config.h包含具體的與操作系統有關(guān)的頭文件src/os/unix/ngx__config.h,這個(gè)頭文件中包含幾乎所有需要的系統頭文件,并包含了objs/ngx_auto_config.h,這是自動(dòng)腳本生成的另一個(gè)頭文件,其中包含一些宏定義,這些宏定義說(shuō)明當前操作系統中存在的特性,根據這些特性又判斷出可以支持的nginx內嵌模塊情況,比如 rewrite模塊需要pcre的支持等。

src/core/ngx_core.h文件中包含的是nginx中幾乎所有實(shí)現代碼的頭文件,包含這個(gè)頭文件,就可以訪(fǎng)問(wèn)nginx中的所有數據結構和函數接口。

幾乎所有的模塊都包含了ngx_config.h和ngx_core.h。所以,在nginx的任何實(shí)現代碼中,可以直接使用很多操作系統的接口和nginx實(shí)現的接口。

縱觀(guān)整個(gè)nginx的代碼,可以大致分為三塊,一塊是一些重要的數據結構及其操作接口,代碼主要在src/core和src/os/unix目錄下;第二塊是模塊的實(shí)現,四個(gè)種類(lèi)幾十個(gè)模塊的實(shí)現分散在src/core、src/event、src/http、src/mail目錄下;第三塊是啟動(dòng)過(guò)程的代碼,上一篇也大致列了一下啟動(dòng)調用的函數序列,其代碼分布在src/core和src/os/unix目錄下。

0.8.9版本nginx的核心類(lèi)模塊有7個(gè),event類(lèi)模塊有10個(gè),http類(lèi)模塊有47個(gè),mail類(lèi)模塊有7個(gè)。另外還有一個(gè)模塊是沒(méi)有上下文的,是conf模塊,所以準確的說(shuō)nginx的模塊有五種:

core/ngx_conf_file.h:#define NGX_CORE_MODULE 0×45524F43 /* “CORE” */
core/ngx_conf_file.h:#define NGX_CONF_MODULE 0×464E4F43 /* “CONF” */
event/ngx_event.h:#define NGX_EVENT_MODULE 0×544E5645 /* “EVNT” */
http/ngx_http_config.h:#define NGX_HTTP_MODULE 0×50545448 /* “HTTP” */
mail/ngx_mail.h:#define NGX_MAIL_MODULE 0×4C49414D /* “MAIL” */

上一篇簡(jiǎn)單的走了一遍nginx的啟動(dòng)流程,列了一下函數調用序列,流程中最重要的是三個(gè)函數:main(src/core /nginx.c)->ngx_init_cycle(src/core /ngx_cycle.c)->ngx_master_process_cycle(src/os/unix /ngx_process_cycle.c)。

下面會(huì )更深入細致的分析一下啟動(dòng)流程,重點(diǎn)圍繞上述三個(gè)函數進(jìn)行。

nginx的啟動(dòng)過(guò)程是從設置一個(gè)變量開(kāi)始的:ngx_cycle_t *ngx_cycle(src/core/ngx_cycle.c),ngx_cycle_t是一個(gè)重要的數據結構,它是一個(gè)很重要的容器,保存了一些全局性質(zhì)的數據,在整個(gè)系統內都會(huì )被使用到。

struct ngx_cycle_s {
void ****conf_ctx;
ngx_pool_t *pool;

ngx_log_t *log;
ngx_log_t new_log;

ngx_connection_t **files;
ngx_connection_t *free_connections;
ngx_uint_t free_connection_n;

ngx_array_t listening;
ngx_array_t pathes;
ngx_list_t open_files;
ngx_list_t shared_memory;

ngx_uint_t connection_n;
ngx_uint_t files_n;

ngx_connection_t *connections;
ngx_event_t *read_events;
ngx_event_t *write_events;

ngx_cycle_t *old_cycle;

ngx_str_t conf_file;
ngx_str_t conf_param;
ngx_str_t conf_prefix;
ngx_str_t prefix;
ngx_str_t lock_file;
ngx_str_t hostname;
};(見(jiàn)src/core/ngx_cycle.h)

下面從main開(kāi)始詳細分析啟動(dòng)流程中處理過(guò)程,在遇到設置ngx_cycle_t字段的時(shí)候再詳細解釋這個(gè)字段。

main函數的處理過(guò)程可以分為以下步驟:

1、從控制臺獲取參數并處理:ngx_get_options(argc, argv);

2、簡(jiǎn)單初始化,初始化一些數據結構和模塊:ngx_debug_init(),ngx_time_init(),ngx_regex_init(),ngx_log_init(),ngx_ssl_init();

3、初始化局部的ngx_cycle_t init_cycle結構體變量:
ngx_memzero(&init_cycle, sizeof(ngx_cycle_t));
init_cycle.log = log;
ngx_cycle = &init_cycle;

init_cycle.pool = ngx_create_pool(1024, log);
if (init_cycle.pool == NULL) {
return 1;
}

4、保存參數,設置幾個(gè)全局變量:ngx_argc,ngx_os_argv,ngx_argv,ngx_os_environ;

5、調用ngx_process_options,設置init_cycle的一些字段,這些字段是從控制臺的命令中取得的:conf_prefix(config prefix path)、prefix(prefix path:-p prefix)、conf_file(配置文件路徑:-c filenname)、conf_param(-g directives),另外還把init_cycle.log.log_level設置為NGX_LOG_INFO;

6、調用ngx_os_init,這個(gè)調用會(huì )設置一些全局變量,這些全局變量和操作系統相關(guān),比如:ngx_pagesize,ngx_cacheline_size,ngx_ncpu,ngx_cpuinfo(),ngx_max_sockets等;

7、調用初始化函數ngx_crc32_table_init();

8、調用ngx_set_inherited_sockets(&init_cycle),初始化init_cycle.listening,這是一個(gè)ngx_listening_t的結構數組,其socket_fd是從環(huán)境變量NGINX中讀取的;

9、對系統所有模塊點(diǎn)一下數,然后進(jìn)入ngx_init_cycle作主要的模塊相關(guān)的初始化,init_cycle作為舊的全局設置傳進(jìn)去,這個(gè)函數會(huì )創(chuàng )建一下新的ngx_cycle_t變量,并返回其指針:
ngx_max_module = 0;
for (i = 0; ngx_modules[i]; i++) {
ngx_modules[i]->index = ngx_max_module++;
}

cycle = ngx_init_cycle(&init_cycle);

10、與信號量相關(guān)的一些操作代碼;

11、多進(jìn)程的情況下,調用ngx_master_process_cycle(cycle),單進(jìn)程情況下調用ngx_single_process_cycle完成最后的啟動(dòng)工作。

ngx_init_cycle函數的處理過(guò)程分為以下步驟:

1、調用ngx_timezone_update()、ngx_timeofday()和ngx_time_update(0, 0)做時(shí)間校準,nginx的時(shí)間以及計數器相關(guān)的內容以后作專(zhuān)題介紹吧;

2、創(chuàng )建一個(gè)新的ngx_cycle_t變量cycle,并且初始化其大部分的成員字段,有一些是從傳入的old_cycle直接拷貝過(guò)來(lái)的,這些字段包括:log,conf_prefix,prefix,conf_file,conf_param;還有一些字段會(huì )判斷一下old_cycle中是否存在,如果存在,則取得這些字段的占用空間,在cycle中申請等大的空間,并初始化(不拷貝),否則就申請默認大小的空間,這些字段有:pathes,open_files,share_memory,listening;還有一些字段是重新創(chuàng )建或者第一次賦值的:pool,new_log.log_level(=NGX_LOG_ERR),old_cycle(=old_cycle),hostname(gethostname);
最重要的一個(gè)字段是conf_ctx,它被初始化為ngx_max_module個(gè)void *指針,這預示著(zhù)conf_ctx是所有模塊的配置結構的指針數組;

3、調用所有核心類(lèi)模塊的鉤子create_conf,并把返回的配置結構指針?lè )诺絚onf_ctx數組中,偏移位置為ngx_module_t.index;

4、從命令行和配置文件中把所有配置更新到cycle的conf_ctx中,首先調用ngx_conf_param把命令行中的指令(-g directives)轉換為配置結構并把指針加入到cycle.conf_ctx中,接著(zhù)調用ngx_conf_parse(..,filename) 把配置文件中的指令轉換為配置結構并把指針加入到cycle.conf_ctx中。

ngx_conf_param最后也會(huì )調用 ngx_conf_parse(..,NULL),所以配置的更新主要是在ngx_conf_parse中進(jìn)行的,這個(gè)函數中有一個(gè)for循環(huán),每次循環(huán)調用ngx_conf_read_token取得一個(gè)配置指令(以;結尾),然后調用ngx_conf_handler處理這條指令,ngx_conf_handler每次都會(huì )遍歷所有模塊的指令集,查找這條配置指令并分析其合法性,如果指令正確,則會(huì )創(chuàng )建配置結構并把指針加入到 cycle.conf_ctx中,配置結構的賦值是調用該指令的鉤子set完成的。

遍歷指令集的過(guò)程首先是遍歷所有的核心類(lèi)模塊,若是 event類(lèi)的指令,則會(huì )遍歷到ngx_events_module,這個(gè)模塊是屬于核心類(lèi)的,其鉤子set又會(huì )嵌套調用ngx_conf_parse去遍歷所有的event類(lèi)模塊,同樣的,若是http類(lèi)指令,則會(huì )遍歷到ngx_http_module,該模塊的鉤子set進(jìn)一步遍歷所有的http類(lèi)模塊,mail類(lèi)指令會(huì )遍歷到ngx_mail_module,該模塊的鉤子進(jìn)一步遍歷到所有的mail類(lèi)模塊。要特別注意的是:這三個(gè)遍歷過(guò)程中會(huì )在適當的時(shí)機調用event類(lèi)模塊、http類(lèi)模塊和mail類(lèi)模塊的創(chuàng )建配置和初始化配置的鉤子。從這里可以看出,event、http、mail三類(lèi)模塊的鉤子是配置中的指令驅動(dòng)的;

5、獲得核心模塊ngx_core_dodule的配置結構,然后調用ngx_create_pidfile 創(chuàng )建pid文件。獲取配置結構的代碼:ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module),這里的ngx_get_conf是一個(gè)宏定義:#define ngx_get_conf(conf_ctx, module) conf_ctx[module.index];

6、調用 ngx_test_lockfile(filename,log),ngx_create_pathes(cycle,user),接著(zhù)打開(kāi) errorlog文件并賦值給cycle->new_log.file:cycle->new_log.file = ngx_conf_open_file(cycle, &error_log);

7、打開(kāi)新文件,在第2步的時(shí)候提到 cycle->open_files這個(gè)鏈表是空的,只是給它預先分配了空間,并沒(méi)有數據,這里之所以可能會(huì )有文件被打開(kāi),估計是前面讀配置文件的時(shí)候,調用各個(gè)鉤子的過(guò)程中,填充了這個(gè)鏈表,把ngx_open_file_t結構變量填充進(jìn)來(lái)(結構體中包含要打開(kāi)文件的路徑信息),這是我猜測的,之后再驗證:)接著(zhù)修改一下cycle的成員:cycle->log = &cycle->new_log;pool->log = &cycle->new_log;

8、創(chuàng )建共享內存,和open_files類(lèi)似,在第2步的時(shí)候cycle->share_memory也初始化為一個(gè)空的鏈表,也是預分配了空間,如果此時(shí)鏈表中已經(jīng)被填充了ngx_shm_zone_t結構變量(其中包含需要共享內存的尺寸和標識等信息),那么這里就會(huì )分配共享內存,并且調用合適的初始化鉤子初始化分配的共享內存,每塊共享內存都會(huì )有name標識,這里也會(huì )做一些排重,已經(jīng)分配的就不會(huì )再去分配,從對open_files和 share_memory的處理過(guò)程可以看出,nginx在資源管理上是集中分配的,請求資源的時(shí)候分配說(shuō)明性的結構變量,然后在恰當的時(shí)機才去真正分配資源;

9、處理listening sockets,cycle->listening是ngx_listening_t結構的數組,把cycle->listening于 old_cycle->listening進(jìn)行比較,設置cycle->listening的一些狀態(tài)信息,接著(zhù)調用 ngx_open_listening_sockets(cycle)啟動(dòng)cycle->listening中的所有監聽(tīng)socket,循環(huán)調用 socket,bind,listen完成服務(wù)端監聽(tīng)socket的啟動(dòng)。接著(zhù)調用 ngx_configure_listening_sockets(cycle)配置監聽(tīng)socket,會(huì )根據ngx_listening_t中的狀態(tài)信息設置socket的讀寫(xiě)緩存和TCP_DEFER_ACCEPT;

10、調用所有模塊的鉤子init_module;

11、關(guān)閉或者刪除一些殘留在old_cycle中的資源,首先釋放不用的共享內存,接著(zhù)關(guān)閉不使用的監聽(tīng)socket,再關(guān)閉不使用的打開(kāi)文件,最后把 old_cycle放入ngx_old_cycles中,這是一個(gè)ngx_cycle_t *的數組,最后設定一個(gè)定時(shí)器,定期回調ngx_cleaner_event清理ngx_old_cycles,這里設置了30000ms清理一次。

ngx_master_process_cycle的分析留待下一篇,這個(gè)函數會(huì )啟動(dòng)工作進(jìn)程干活,并且會(huì )處理信號量,處理的過(guò)程中會(huì )殺死或者創(chuàng )建新的進(jìn)程。


nginx的進(jìn)程啟動(dòng)過(guò)程是在ngx_master_process_cycle(src/os/unix/ngx_process_cycle.c)中完成的(單進(jìn)程是通過(guò)ngx_single_process_cycle完成,這里只分析多進(jìn)程的情況),在 ngx_master_process_cycle中,會(huì )根據配置文件的worker_processes值創(chuàng )建多個(gè)子進(jìn)程,即一個(gè)master進(jìn)程和多個(gè)worker進(jìn)程。worker進(jìn)程之間、master與worker之間、master與外部之間保持通信,worker之間以及master與 worker之間是通過(guò)socketpair進(jìn)行通信的,socketpair是一對全雙工的無(wú)名socket,可以當作管道使用,和管道不同的是,每條 socket既可以讀也可以寫(xiě),而管道只能用于寫(xiě)或者用于讀;master與外部之間是通過(guò)信號通信的。

master進(jìn)程主要進(jìn)行一些全局性的初始化工作和管理worker的工作;事件處理是在worker中進(jìn)行的。

進(jìn)程啟動(dòng)的過(guò)程中,有一些重要的全局數據會(huì )被設置,最重要的是進(jìn)程表ngx_processes,master每創(chuàng )建一個(gè)worker都會(huì )把一個(gè)設置好的 ngx_process_t結構變量放入ngx_processes中,進(jìn)程表長(cháng)度為1024,剛創(chuàng )建的進(jìn)程存放在ngx_process_slot位置,ngx_last_process是進(jìn)程表中最后一個(gè)存量進(jìn)程的下一個(gè)位置,ngx_process_t是進(jìn)程在nginx中的抽象:

typedef void (*ngx_spawn_proc_pt) (ngx_cycle_t *cycle, void *data);

typedef struct {
ngx_pid_t pid;
int status;
ngx_socket_t channel[2];

ngx_spawn_proc_pt proc;
void *data;
char *name;

unsigned respawn:1;
unsigned just_spawn:1;
unsigned detached:1;
unsigned exiting:1;
unsigned exited:1;
} ngx_process_t;(src/os/unix/ngx_process.h)

pid是進(jìn)程的id;

status是進(jìn)程的退出狀態(tài);

channel[2]是socketpair創(chuàng )建的一對socket句柄;

proc是進(jìn)程的執行函數,data為proc的參數;

最后的幾個(gè)位域是進(jìn)程的狀態(tài),respawn:重新創(chuàng )建的、just_spawn:第一次創(chuàng )建的、detached:分離的、exiting:正在退出、exited:已經(jīng)退出。

進(jìn)程間通信是利用socketpair創(chuàng )建的一對socket進(jìn)行的,通信中傳輸的是ngx_channel_t結構變量:

typedef struct {
ngx_uint_t command;
ngx_pid_t pid;
ngx_int_t slot;
ngx_fd_t fd;
} ngx_channel_t;(src/os/unix/ngx_channel.h)

command是要發(fā)送的命令,有5種:

#define NGX_CMD_OPEN_CHANNEL 1
#define NGX_CMD_CLOSE_CHANNEL 2
#define NGX_CMD_QUIT 3
#define NGX_CMD_TERMINATE 4
#define NGX_CMD_REOPEN 5

pid是發(fā)送方進(jìn)程的進(jìn)程id;

slot是發(fā)送方進(jìn)程在進(jìn)程表中偏移位置;

fd是發(fā)送給對方的句柄。

進(jìn)程的啟動(dòng)過(guò)程是比較重要的一個(gè)環(huán)節,為了把這個(gè)過(guò)程分析透徹,下面會(huì )多采用注釋代碼的方式分析。

首先分析ngx_master_process_cycle函數,可以分解為以下各步驟:

1、master設置一些需要處理的信號,這些信號包括SIGCHLD,SIGALRM,SIGIO,SIGINT,NGX_RECONFIGURE_SIGNAL(SIGHUP),NGX_REOPEN_SIGNAL(SIGUSR1),
NGX_NOACCEPT_SIGNAL(SIGWINCH),NGX_TERMINATE_SIGNAL(SIGTERM),NGX_SHUTDOWN_SIGNAL(SIGQUIT),
NGX_CHANGEBIN_SIGNAL(SIGUSR2);

2、調用ngx_setproctilte設置進(jìn)程標題,title = “master process” + ngx_argv[0] + … + ngx_argv[ngx_argc-1];

3、調用ngx_start_worker_processes(cycle, ccf->worker_processes, NGX_PROCESS_RESPAWN)啟動(dòng)worker進(jìn)程;

4、調用ngx_start_cache_manager_processes(cycle, 0)啟動(dòng)文件cache管理進(jìn)程,有些模塊需要文件cache,比如fastcgi模塊,這些模塊會(huì )把文件cache路徑添加到 cycle->paths中,文件cache管理進(jìn)程會(huì )定期調用這些模塊的文件cache處理鉤子處理一下文件cache;

5、master循環(huán)處理信號量。
ngx_new_binary = 0;
delay = 0;
live = 1;

for ( ;; ) {
// delay用來(lái)設置等待worker退出的時(shí)間,master接收了退出信號后首先發(fā)送退出信號給worker,
// 而worker退出需要一些時(shí)間
if (delay) {
delay *= 2;

ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
“temination cycle: %d”, delay);

itv.it_interval.tv_sec = 0;
itv.it_interval.tv_usec = 0;
itv.it_value.tv_sec = delay / 1000;
itv.it_value.tv_usec = (delay % 1000 ) * 1000;

// 設置定時(shí)器
if (setitimer(ITIMER_REAL, &itv, NULL) == -1) {
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
“setitimer() failed”);
}
}

ngx_log_debug0(NGX_LOG_DEBUG_EVENT, cycle->log, 0, “sigsuspend”);

// 掛起信號量,等待定時(shí)器
sigsuspend(&set);

ngx_time_update(0, 0);

ngx_log_debug0(NGX_LOG_DEBUG_EVENT, cycle->log, 0, “wake up”);

// 收到了SIGCHLD信號,有worker退出(ngx_reap==1)
if (ngx_reap) {
ngx_reap = 0;
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, cycle->log, 0, “reap children”);

// 處理所有worker,如果有worker異常退出則重啟這個(gè)worker,如果所有worker都退出
// 返回0賦值給live
live = ngx_reap_children(cycle);
}

// 如果worker都已經(jīng)退出,
// 并且收到了NGX_CMD_TERMINATE命令或者SIGTERM信號或者SIGINT信號(ngx_terminate=1)
// 或者NGX_CMD_QUIT命令或者SIGQUIT信號(ngx_quit=1),則master退出
if (!live && (ngx_terminate || ngx_quit)) {
ngx_master_process_exit(cycle);
}

// 收到了NGX_CMD_TERMINATE命令或者SIGTERM信號或者SIGINT信號,
// 通知所有worker退出,并且等待worker退出
if (ngx_terminate) {
// 設置延時(shí)
if (delay == 0) {
delay = 50;
}

if (delay > 1000) {
// 延時(shí)已到,給所有worker發(fā)送SIGKILL信號,強制殺死worker
ngx_signal_worker_processes(cycle, SIGKILL);
} else {
// 給所有worker發(fā)送SIGTERM信號,通知worker退出
ngx_signal_worker_processes(cycle,
ngx_signal_value(NGX_TERMINATE_SIGNAL));
}

continue;
}

// 收到了NGX_CMD_QUIT命令或者SIGQUIT信號
if (ngx_quit) {
// 給所有worker發(fā)送SIGQUIT信號
ngx_signal_worker_processes(cycle,
ngx_signal_value(NGX_SHUTDOWN_SIGNAL));

// 關(guān)閉所有監聽(tīng)的socket
ls = cycle->listening.elts;
for (n = 0; n < cycle->listening.nelts; n++) {
if (ngx_close_socket(ls[n].fd) == -1) {
ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_socket_errno,
ngx_close_socket_n ” %V failed”,
&ls[n].addr_text);
}
}
cycle->listening.nelts = 0;

continue;
}

// 收到了SIGHUP信號
if (ngx_reconfigure) {
ngx_reconfigure = 0;

// 代碼已經(jīng)被替換,重啟worker,不需要重新初始化配置
if (ngx_new_binary) {
ngx_start_worker_processes(cycle, ccf->worker_processes,
NGX_PROCESS_RESPAWN);
ngx_start_cache_manager_processes(cycle, 0);
ngx_noaccepting = 0;

continue;
}

ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, “reconfiguring”);

// 重新初始化配置
cycle = ngx_init_cycle(cycle);
if (cycle == NULL) {
cycle = (ngx_cycle_t *) ngx_cycle;
continue;
}

// 重啟worker
ngx_cycle = cycle;
ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx,
ngx_core_module);
ngx_start_worker_processes(cycle, ccf->worker_processes,
NGX_PROCESS_JUST_RESPAWN);
ngx_start_cache_manager_processes(cycle, 1);
live = 1;
ngx_signal_worker_processes(cycle,
ngx_signal_value(NGX_SHUTDOWN_SIGNAL));
}

// 當ngx_noaccepting=1的時(shí)候會(huì )把ngx_restart設為1,重啟worker
if (ngx_restart) {
ngx_restart = 0;
ngx_start_worker_processes(cycle, ccf->worker_processes,
NGX_PROCESS_RESPAWN);
ngx_start_cache_manager_processes(cycle, 0);
live = 1;
}

// 收到SIGUSR1信號,重新打開(kāi)log文件
if (ngx_reopen) {
ngx_reopen = 0;
ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, “reopening logs”);
ngx_reopen_files(cycle, ccf->user);
ngx_signal_worker_processes(cycle,
ngx_signal_value(NGX_REOPEN_SIGNAL));
}

// 收到SIGUSR2信號,熱代碼替換
if (ngx_change_binary) {
ngx_change_binary = 0;
ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, “changing binary”);
// 調用execve執行新的代碼
ngx_new_binary = ngx_exec_new_binary(cycle, ngx_argv);
}

// 收到SIGWINCH信號,不再接收請求,worker退出,master不退出
if (ngx_noaccept) {
ngx_noaccept = 0;
ngx_noaccepting = 1;
ngx_signal_worker_processes(cycle,
ngx_signal_value(NGX_SHUTDOWN_SIGNAL));
}
}

真正創(chuàng )建worker子進(jìn)程的函數是ngx_start_worker_processes,這個(gè)函數本身很簡(jiǎn)單:

static void
ngx_start_worker_processes(ngx_cycle_t *cycle, ngx_int_t n, ngx_int_t type)
{
ngx_int_t i;
ngx_channel_t ch;

ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, “start worker processes”);

// 傳遞給其他worker子進(jìn)程的命令:打開(kāi)通信管道
ch.command = NGX_CMD_OPEN_CHANNEL;

// 創(chuàng )建n個(gè)worker子進(jìn)程
for (i = 0; i < n; i++) {

cpu_affinity = ngx_get_cpu_affinity(i);

// ngx_spawn_process創(chuàng )建worker子進(jìn)程并初始化相關(guān)資源和屬性,
// 然后執行子進(jìn)程的執行函數ngx_worker_process_cycle
ngx_spawn_process(cycle, ngx_worker_process_cycle, NULL,
"worker process", type);

// master父進(jìn)程向所有已經(jīng)創(chuàng )建的worker子進(jìn)程(不包括本子進(jìn)程)廣播消息,
// 告知當前worker子進(jìn)程的進(jìn)程id、在進(jìn)程表中的位置和管道句柄;這些worker子進(jìn)程收到消息后,
// 會(huì )更新這些消息到自己進(jìn)程空間的進(jìn)程表,這樣就可以實(shí)現任意兩個(gè)worker子進(jìn)程之間的通信了
// 好像worker子進(jìn)程沒(méi)有辦法向master進(jìn)程發(fā)送消息?
ch.pid = ngx_processes[ngx_process_slot].pid;
ch.slot = ngx_process_slot;
ch.fd = ngx_processes[ngx_process_slot].channel[0];

ngx_pass_open_channel(cycle, &ch);
}
}

把ngx_pass_open_channel展開(kāi)如下:

static void
ngx_pass_open_channel(ngx_cycle_t *cycle, ngx_channel_t *ch)
{
ngx_int_t i;

for (i = 0; i < ngx_last_process; i++) {

// 跳過(guò)自己和異常的worker
if (i == ngx_process_slot
|| ngx_processes[i].pid == -1
|| ngx_processes[i].channel[0] == -1)
{
continue;
}

ngx_log_debug6(NGX_LOG_DEBUG_CORE, cycle->log, 0,
“pass channel s:%d pid:%P fd:%d to s:%i pid:%P fd:%d”,
ch->slot, ch->pid, ch->fd,
i, ngx_processes[i].pid,
ngx_processes[i].channel[0]);

/* TODO: NGX_AGAIN */

// 發(fā)送消息給其他的worker
ngx_write_channel(ngx_processes[i].channel[0],
ch, sizeof(ngx_channel_t), cycle->log);
}
}

第三個(gè)要剖開(kāi)的函數是創(chuàng )建子進(jìn)程的ngx_pid_t ngx_spawn_process(ngx_cycle_t *cycle, ngx_spawn_proc_pt proc, void *data, char *name, ngx_int_t respawn),這個(gè)函數定義在src/os/unix/ngx_process.c中,proc是子進(jìn)程的執行函數,data是其參數,name是子進(jìn)程的名字。

{
u_long on;
ngx_pid_t pid;
ngx_int_t s; // 將要創(chuàng )建的子進(jìn)程在進(jìn)程表中的位置

if (respawn >= 0) {
// 替換進(jìn)程ngx_processes[respawn],可安全重用該進(jìn)程表項
s = respawn;

} else {
// 先找到一個(gè)被回收的進(jìn)程表項
for (s = 0; s < ngx_last_process; s++) {
if (ngx_processes[s].pid == -1) {
break;
}
}

// 進(jìn)程表已滿(mǎn)
if (s == NGX_MAX_PROCESSES) {
ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,
“no more than %d processes can be spawned”,
NGX_MAX_PROCESSES);
return NGX_INVALID_PID;
}
}

// 不是分離的子進(jìn)程
if (respawn != NGX_PROCESS_DETACHED) {

/* Solaris 9 still has no AF_LOCAL */

// 創(chuàng )建一對已經(jīng)連接的無(wú)名socket(管道句柄)
if (socketpair(AF_UNIX, SOCK_STREAM, 0, ngx_processes[s].channel) == -1)
{
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
“socketpair() failed while spawning \”%s\”", name);
return NGX_INVALID_PID;
}

ngx_log_debug2(NGX_LOG_DEBUG_CORE, cycle->log, 0,
“channel %d:%d”,
ngx_processes[s].channel[0],
ngx_processes[s].channel[1]);

// 設置管道句柄為非阻塞模式
if (ngx_nonblocking(ngx_processes[s].channel[0]) == -1) {
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
ngx_nonblocking_n ” failed while spawning \”%s\”",
name);
ngx_close_channel(ngx_processes[s].channel, cycle->log);
return NGX_INVALID_PID;
}

if (ngx_nonblocking(ngx_processes[s].channel[1]) == -1) {
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
ngx_nonblocking_n ” failed while spawning \”%s\”",
name);
ngx_close_channel(ngx_processes[s].channel, cycle->log);
return NGX_INVALID_PID;
}

// 打開(kāi)異步模式
on = 1;
if (ioctl(ngx_processes[s].channel[0], FIOASYNC, &on) == -1) {
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
“ioctl(FIOASYNC) failed while spawning \”%s\”", name);
ngx_close_channel(ngx_processes[s].channel, cycle->log);
return NGX_INVALID_PID;
}

// 設置異步io的所有者
if (fcntl(ngx_processes[s].channel[0], F_SETOWN, ngx_pid) == -1) {
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
“fcntl(F_SETOWN) failed while spawning \”%s\”", name);
ngx_close_channel(ngx_processes[s].channel, cycle->log);
return NGX_INVALID_PID;
}

// 執行了exec后關(guān)閉管道句柄
if (fcntl(ngx_processes[s].channel[0], F_SETFD, FD_CLOEXEC) == -1) {
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
“fcntl(FD_CLOEXEC) failed while spawning \”%s\”",
name);
ngx_close_channel(ngx_processes[s].channel, cycle->log);
return NGX_INVALID_PID;
}

if (fcntl(ngx_processes[s].channel[1], F_SETFD, FD_CLOEXEC) == -1) {
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
“fcntl(FD_CLOEXEC) failed while spawning \”%s\”",
name);
ngx_close_channel(ngx_processes[s].channel, cycle->log);
return NGX_INVALID_PID;
}

// 設置當前子進(jìn)程的管道句柄
ngx_channel = ngx_processes[s].channel[1];

} else {
ngx_processes[s].channel[0] = -1;
ngx_processes[s].channel[1] = -1;
}

// 設置當前子進(jìn)程的進(jìn)程表項索引
ngx_process_slot = s;

// 創(chuàng )建子進(jìn)程
pid = fork();

switch (pid) {

case -1:
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
“fork() failed while spawning \”%s\”", name);
ngx_close_channel(ngx_processes[s].channel, cycle->log);
return NGX_INVALID_PID;

case 0:
// 設置當前子進(jìn)程的進(jìn)程id
ngx_pid = ngx_getpid();
// 子進(jìn)程運行執行函數
proc(cycle, data);
break;

default:
break;
}

ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, “start %s %P”, name, pid);

// 設置一些進(jìn)程表項字段
ngx_processes[s].pid = pid;
ngx_processes[s].exited = 0;

// 替換進(jìn)程ngx_processes[respawn],不用設置其他進(jìn)程表項字段了
if (respawn >= 0) {
return pid;
}

// 設置其他的進(jìn)程表項字段
ngx_processes[s].proc = proc;
ngx_processes[s].data = data;
ngx_processes[s].name = name;
ngx_processes[s].exiting = 0;

// 設置進(jìn)程表項的一些狀態(tài)字段
switch (respawn) {

case NGX_PROCESS_NORESPAWN:
ngx_processes[s].respawn = 0;
ngx_processes[s].just_spawn = 0;
ngx_processes[s].detached = 0;
break;

case NGX_PROCESS_JUST_SPAWN:
ngx_processes[s].respawn = 0;
ngx_processes[s].just_spawn = 1;
ngx_processes[s].detached = 0;
break;

case NGX_PROCESS_RESPAWN:
ngx_processes[s].respawn = 1;
ngx_processes[s].just_spawn = 0;
ngx_processes[s].detached = 0;
break;

case NGX_PROCESS_JUST_RESPAWN:
ngx_processes[s].respawn = 1;
ngx_processes[s].just_spawn = 1;
ngx_processes[s].detached = 0;
break;

// 分離的子進(jìn)程,不受master控制?
case NGX_PROCESS_DETACHED:
ngx_processes[s].respawn = 0;
ngx_processes[s].just_spawn = 0;
ngx_processes[s].detached = 1;
break;
}

if (s == ngx_last_process) {
ngx_last_process++;
}

return pid;
}

go on

緊接上回。

第四個(gè)剖析的是worker子進(jìn)程的執行函數ngx_worker_process_cycle(src/os/unix/ngx_process_cycle.c)。

static void
ngx_worker_process_cycle(ngx_cycle_t *cycle, void *data)
{
ngx_uint_t i;
ngx_connection_t *c;

// 初始化
ngx_worker_process_init(cycle, 1);

ngx_setproctitle(”worker process”);

#if (NGX_THREADS)
// 略去關(guān)于線(xiàn)程的代碼
#endif

for ( ;; ) {

// 退出狀態(tài)已設置,關(guān)閉所有連接
if (ngx_exiting) {

c = cycle->connections;

for (i = 0; i < cycle->connection_n; i++) {

/* THREAD: lock */

if (c[i].fd != -1 && c[i].idle) {
c[i].close = 1;
c[i].read->handler(c[i].read);
}
}

if (ngx_event_timer_rbtree.root == ngx_event_timer_rbtree.sentinel)
{
ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, “exiting”);

ngx_worker_process_exit(cycle);
}
}

ngx_log_debug0(NGX_LOG_DEBUG_EVENT, cycle->log, 0, “worker cycle”);

// 處理事件和計時(shí)
ngx_process_events_and_timers(cycle);

// 收到NGX_CMD_TERMINATE命令
if (ngx_terminate) {
ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, “exiting”);

// 清理后進(jìn)程退出,會(huì )調用所有模塊的鉤子exit_process
ngx_worker_process_exit(cycle);
}

// 收到NGX_CMD_QUIT命令
if (ngx_quit) {
ngx_quit = 0;
ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0,
“gracefully shutting down”);
ngx_setproctitle(”worker process is shutting down”);

if (!ngx_exiting) {
// 關(guān)閉監聽(tīng)socket,設置退出狀態(tài)
ngx_close_listening_sockets(cycle);
ngx_exiting = 1;
}
}

// 收到NGX_CMD_REOPEN命令,重新打開(kāi)log
if (ngx_reopen) {
ngx_reopen = 0;
ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, “reopening logs”);
ngx_reopen_files(cycle, -1);
}
}
}

worker子進(jìn)程的初始化函數是ngx_worker_process_init(ngx_cycle_t *cycle, ngx_uint_t priority),這個(gè)函數可分解為以下步驟:

1、設置ngx_process = NGX_PROCESS_WORKER,在master進(jìn)程中這個(gè)變量被設置為NGX_PROCESS_MASTER;

2、全局性的設置,根據全局的配置信息設置執行環(huán)境、優(yōu)先級、限制、setgid、setuid、信號初始化等;

3、調用所有模塊的鉤子init_process;

4、關(guān)閉不使用的管道句柄,關(guān)閉當前worker子進(jìn)程(本進(jìn)程)的channel[0]句柄和繼承來(lái)的其他進(jìn)程的channel[1]句柄,本進(jìn)程會(huì )使用其他進(jìn)程的channel[0]句柄發(fā)送消息,使用本進(jìn)程的channel[1]句柄監聽(tīng)事件:

for (n = 0; n < ngx_last_process; n++) {

if (ngx_processes[n].pid == -1) {
continue;
}

if (n == ngx_process_slot) {
continue;
}

if (ngx_processes[n].channel[1] == -1) {
continue;
}

if (close(ngx_processes[n].channel[1]) == -1) {
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
“close() channel failed”);
}
}

if (close(ngx_processes[ngx_process_slot].channel[0]) == -1) {
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
“close() channel failed”);
}

5、在本進(jìn)程的channel[1]句柄監聽(tīng)事件:

if (ngx_add_channel_event(cycle, ngx_channel, NGX_READ_EVENT,
ngx_channel_handler)
== NGX_ERROR)
{
/* fatal */
exit(2);
}
ngx_add_channel_event把句柄ngx_channel(本進(jìn)程的channel[1])上建立的連接的可讀事件加入事件監控隊列,事件處理函數為ngx_channel_hanlder(ngx_event_t *ev)。當有可讀事件的時(shí)候(master或者其他worker子進(jìn)程調用ngx_write_channel向本進(jìn)程的管道句柄發(fā)送了消息),ngx_channel_handler負責處理消息,過(guò)程如下:

static void
ngx_channel_handler(ngx_event_t *ev)
{
ngx_int_t n;
ngx_channel_t ch;
ngx_connection_t *c;

if (ev->timedout) {
ev->timedout = 0;
return;
}

c = ev->data;

ngx_log_debug0(NGX_LOG_DEBUG_CORE, ev->log, 0, “channel handler”);

for ( ;; ) {

// 從管道句柄中讀取消息
n = ngx_read_channel(c->fd, &ch, sizeof(ngx_channel_t), ev->log);

ngx_log_debug1(NGX_LOG_DEBUG_CORE, ev->log, 0, “channel: %i”, n);

if (n == NGX_ERROR) {

if (ngx_event_flags & NGX_USE_EPOLL_EVENT) {
ngx_del_conn(c, 0);
}

ngx_close_connection(c);
return;
}

if (ngx_event_flags & NGX_USE_EVENTPORT_EVENT) {
if (ngx_add_event(ev, NGX_READ_EVENT, 0) == NGX_ERROR) {
return;
}
}

if (n == NGX_AGAIN) {
return;
}

ngx_log_debug1(NGX_LOG_DEBUG_CORE, ev->log, 0,
“channel command: %d”, ch.command);

// 處理消息命令
switch (ch.command) {

case NGX_CMD_QUIT:
ngx_quit = 1;
break;

case NGX_CMD_TERMINATE:
ngx_terminate = 1;
break;

case NGX_CMD_REOPEN:
ngx_reopen = 1;
break;

case NGX_CMD_OPEN_CHANNEL:

ngx_log_debug3(NGX_LOG_DEBUG_CORE, ev->log, 0,
“get channel s:%i pid:%P fd:%d”,
ch.slot, ch.pid, ch.fd);

ngx_processes[ch.slot].pid = ch.pid;
ngx_processes[ch.slot].channel[0] = ch.fd;
break;

case NGX_CMD_CLOSE_CHANNEL:

ngx_log_debug4(NGX_LOG_DEBUG_CORE, ev->log, 0,
“close channel s:%i pid:%P our:%P fd:%d”,
ch.slot, ch.pid, ngx_processes[ch.slot].pid,
ngx_processes[ch.slot].channel[0]);

if (close(ngx_processes[ch.slot].channel[0]) == -1) {
ngx_log_error(NGX_LOG_ALERT, ev->log, ngx_errno,
“close() channel failed”);
}

ngx_processes[ch.slot].channel[0] = -1;
break;
}
}

nginx的啟動(dòng)過(guò)程就分析完了,上面分析的只是啟動(dòng)過(guò)程的主要邏輯分支,這個(gè)過(guò)程中和每個(gè)模塊相關(guān)的細節留待后續按功能分析的時(shí)候再剖析。

簡(jiǎn)單總結一下。

nginx的啟動(dòng)過(guò)程可以劃分為兩個(gè)部分,第一部分是讀取配置文件并設置全局的配置結構信息以及每個(gè)模塊的配置結構信息,這期間會(huì )調用模塊的 create_conf鉤子和init_conf鉤子;第二部分是創(chuàng )建進(jìn)程和進(jìn)程間通信機制,master進(jìn)程負責管理各個(gè)worker子進(jìn)程,通過(guò) socketpair向子進(jìn)程發(fā)送消息,各個(gè)worker子進(jìn)程服務(wù)利用事件機制處理請求,通過(guò)socketpair與其他子進(jìn)程通信(發(fā)送消息或者接收消息),進(jìn)程啟動(dòng)的各個(gè)適當時(shí)機會(huì )調用模塊的init_module鉤子、init_process鉤子、exit_process鉤子和 exit_master鉤子,init_master鉤子沒(méi)有被調用過(guò)。

nginx的全局變量非常多,這些全局變量作用不一,有些是作為全局的配置信息,比如ngx_cycle;有些是作為重要數據的全局緩存,比如 ngx_processes;有些是狀態(tài)信息,比如ngx_process,ngx_quit。很難對所有的全局變量作出清晰的分類(lèi),而且要注意的是,子進(jìn)程繼承了父進(jìn)程的全局變量之后,子進(jìn)程和父進(jìn)程就會(huì )獨立處理這些全局變量,有些全局量需要在父子進(jìn)程之間同步就要通過(guò)通信方式了,比如 ngx_processes(進(jìn)程表)的同步。

go on

本站僅提供存儲服務(wù),所有內容均由用戶(hù)發(fā)布,如發(fā)現有害或侵權內容,請點(diǎn)擊舉報。
打開(kāi)APP,閱讀全文并永久保存 查看更多類(lèi)似文章
猜你喜歡
類(lèi)似文章
nginx源代碼分析_浪灣(langwan) 一個(gè)思想跳躍的程序員
Nginx學(xué)習之七
Nginx源碼分析:3張圖看懂啟動(dòng)及進(jìn)程工作原理
解剖Nginx·模塊開(kāi)發(fā)篇(5)解讀內置非默認模塊 ngx
handler模塊(100%)
Nginx+Naxsi部署專(zhuān)業(yè)級web應用防火墻
更多類(lèi)似文章 >>
生活服務(wù)
分享 收藏 導長(cháng)圖 關(guān)注 下載文章
綁定賬號成功
后續可登錄賬號暢享VIP特權!
如果VIP功能使用有故障,
可點(diǎn)擊這里聯(lián)系客服!

聯(lián)系客服

欧美性猛交XXXX免费看蜜桃,成人网18免费韩国,亚洲国产成人精品区综合,欧美日韩一区二区三区高清不卡,亚洲综合一区二区精品久久