nginx架构详解(50%)
nginx的源碼目錄結構(100%)
nginx的優秀除了體現在程序結構以及代碼風格上,nginx的源碼組織也同樣簡潔明了,目錄結構層次結構清晰,值得我們去學習。nginx的源碼目錄與nginx的模塊化以及功能的劃分是緊密結合,這也使得我們可以很方便地找到相關功能的代碼。這節先介紹nginx源碼的目錄結構,先對nginx的源碼有一個大致的認識,下節會講解nginx如何編譯。
下面是nginx源碼的目錄結構:
. ├── auto 自動檢測系統環境以及編譯相關的腳本 │ ├── cc 關于編譯器相關的編譯選項的檢測腳本 │ ├── lib nginx編譯所需要的一些庫的檢測腳本 │ ├── os 與平臺相關的一些系統參數與系統調用相關的檢測 │ └── types 與數據類型相關的一些輔助腳本 ├── conf 存放默認配置文件,在make install后,會拷貝到安裝目錄中去 ├── contrib 存放一些實用工具,如geo配置生成工具(geo2nginx.pl) ├── html 存放默認的網頁文件,在make install后,會拷貝到安裝目錄中去 ├── man nginx的man手冊 └── src 存放nginx的源代碼├── core nginx的核心源代碼,包括常用數據結構的定義,以及nginx初始化運行的核心代碼如main函數├── event 對系統事件處理機制的封裝,以及定時器的實現相關代碼│ └── modules 不同事件處理方式的模塊化,如select、poll、epoll、kqueue等├── http nginx作為http服務器相關的代碼│ └── modules 包含http的各種功能模塊├── mail nginx作為郵件代理服務器相關的代碼├── misc 一些輔助代碼,測試c++頭的兼容性,以及對google_perftools的支持└── os 主要是對各種不同體系統結構所提供的系統函數的封裝,對外提供統一的系統調用接口nginx的configure原理(100%)
nginx的編譯旅程將從configure開始,configure腳本將根據我們輸入的選項、系統環境參與來生成所需的文件(包含源文件與Makefile文件)。configure會調用一系列auto腳本來實現編譯環境的初始化。
auto腳本
auto腳本由一系列腳本組成,他們有一些是實現一些通用功能由其它腳本來調用(如have),有一些則是完成一些特定的功能(如option)。腳本之間的主要執行順序及調用關系如下圖所示(由上到下,表示主流程的執行):
接下來,我們結合代碼來分析下configure的原理:
這是configure源碼開始執行的前三行,依次交由auto目錄下面的option、init、sources來處理。
上面的代碼中,我們選用了文件中的部分代碼進行了說明。大家可結合源碼再進行分析。auto/options的目的主要是處理用戶選項,并由選項生成一些全局變量的值,這些值在其它文件中會用到。該文件也會輸出configure的幫助信息。
該文件的目錄在于初始化一些臨時文件的路徑,檢查echo的兼容性,并創建Makefile。
# 生成最終執行編譯的makefile文件路徑 NGX_MAKEFILE=$NGX_OBJS/Makefile # 動態生成nginx模塊列表的路徑,由于nginx的的一些模塊是可以選擇編譯的,而且可以添加自己的模塊,所以模塊列表是動態生成的 NGX_MODULES_C=$NGX_OBJS/ngx_modules.cNGX_AUTO_HEADERS_H=$NGX_OBJS/ngx_auto_headers.h NGX_AUTO_CONFIG_H=$NGX_OBJS/ngx_auto_config.h# 自動測試目錄與日志輸出文件 NGX_AUTOTEST=$NGX_OBJS/autotest # 如果configure出錯,可用來查找出錯的原因 NGX_AUTOCONF_ERR=$NGX_OBJS/autoconf.errNGX_ERR=$NGX_OBJS/autoconf.err MAKEFILE=$NGX_OBJS/MakefileNGX_PCH= NGX_USE_PCH=# 檢查echo是否支持-n或\c# check the echo's "-n" option and "\c" capabilityif echo "test\c" | grep c >/dev/null; then# 不支持-c的方式,檢查是否支持-n的方式if echo -n test | grep n >/dev/null; thenngx_n=ngx_c=elsengx_n=-nngx_c=fielsengx_n=ngx_c='\c' fi# 創建最初始的makefile文件 # default表示目前編譯對象 # clean表示執行clean工作時,需要刪除makefile文件以及objs目錄 # 整個過程中只會生成makefile文件以及objs目錄,其它所有臨時文件都在objs目錄之下,所以執行clean后,整個目錄還原到初始狀態 # 要再次執行編譯,需要重新執行configure命令# create Makefilecat << END > Makefiledefault: buildclean:rm -rf Makefile $NGX_OBJS END該文件從文件名中就可以看出,它的主要功能是跟源文件相關的。它的主要作用是定義不同功能或系統所需要的文件的變量。根據功能,分為CORE/REGEX/EVENT/UNIX/FREEBSD/HTTP等。每一個功能將會由四個變量組成,”_MODULES”表示此功能相關的模塊,最終會輸出到ngx_modules.c文件中,即動態生成需要編譯到nginx中的模塊;”INCS”表示此功能依賴的源碼目錄,查找頭文件的時候會用到,在編譯選項中,會出現在”-I”中;”DEPS”顯示指明在Makefile中需要依賴的文件名,即編譯時,需要檢查這些文件的更新時間;”SRCS”表示需要此功能編譯需要的源文件。
拿core來說:
CORE_MODULES="ngx_core_module ngx_errlog_module ngx_conf_module ngx_emp_server_module ngx_emp_server_core_module"CORE_INCS="src/core"CORE_DEPS="src/core/nginx.h \ src/core/ngx_config.h \ src/core/ngx_core.h \ src/core/ngx_log.h \ src/core/ngx_palloc.h \ src/core/ngx_array.h \ src/core/ngx_list.h \ src/core/ngx_hash.h \ src/core/ngx_buf.h \ src/core/ngx_queue.h \ src/core/ngx_string.h \ src/core/ngx_parse.h \ src/core/ngx_inet.h \ src/core/ngx_file.h \ src/core/ngx_crc.h \ src/core/ngx_crc32.h \ src/core/ngx_murmurhash.h \ src/core/ngx_md5.h \ src/core/ngx_sha1.h \ src/core/ngx_rbtree.h \ src/core/ngx_radix_tree.h \ src/core/ngx_slab.h \ src/core/ngx_times.h \ src/core/ngx_shmtx.h \ src/core/ngx_connection.h \ src/core/ngx_cycle.h \ src/core/ngx_conf_file.h \ src/core/ngx_resolver.h \ src/core/ngx_open_file_cache.h \ src/core/nginx_emp_server.h \ src/core/emp_server.h \ src/core/task_thread.h \ src/core/standard.h \ src/core/dprint.h \ src/core/ngx_crypt.h"CORE_SRCS="src/core/nginx.c \ src/core/ngx_log.c \ src/core/ngx_palloc.c \ src/core/ngx_array.c \ src/core/ngx_list.c \ src/core/ngx_hash.c \ src/core/ngx_buf.c \ src/core/ngx_queue.c \ src/core/ngx_output_chain.c \ src/core/ngx_string.c \ src/core/ngx_parse.c \ src/core/ngx_inet.c \ src/core/ngx_file.c \ src/core/ngx_crc32.c \ src/core/ngx_murmurhash.c \ src/core/ngx_md5.c \ src/core/ngx_rbtree.c \ src/core/ngx_radix_tree.c \ src/core/ngx_slab.c \ src/core/ngx_times.c \ src/core/ngx_shmtx.c \ src/core/ngx_connection.c \ src/core/ngx_cycle.c \ src/core/ngx_spinlock.c \ src/core/ngx_cpuinfo.c \ src/core/ngx_conf_file.c \ src/core/ngx_resolver.c \ src/core/ngx_open_file_cache.c \ src/core/nginx_emp_server.c \ src/core/emp_server.c \ src/core/standard.c \ src/core/task_thread.c \ src/core/dprint.c \ src/core/ngx_crypt.c"如果我們自己寫一個第三方模塊,我們可能會引用到這些變量的值,或對這些變量進行修改,比如添加我們自己的模塊,或添加自己的一個頭文件查找目錄(在第三方模塊的config中),在后面,我們會看到它是如何加框第三方模塊的。 在繼續分析執行流程之前,我們先介紹一些工具腳本。
從代碼中,我們可以看到,這個工具的作用是,將$have變量的值,宏定義為1,并輸出到auto_config文件中。通常我們通過這個工具來控制是否打開某個特性。這個工具在使用前,需要先定義宏的名稱 ,即$have變量。
這段代碼中,可以看出,configure是如何定義一個特性的:通過宏定義,輸出到config頭文件中,然后在程序中可以判斷這個宏是否有定義,來實現不同的特性。
configure文件中繼續向下:
# 編譯器選項 . auto/cc/conf# 頭文件支持宏定義 if [ "$NGX_PLATFORM" != win32 ]; then. auto/headers fi# 操作系統相關的配置的檢測 . auto/os/conf# unix體系下的通用配置檢測 if [ "$NGX_PLATFORM" != win32 ]; then. auto/unix ficonfigure會依次調用其它幾個文件,來進行環境的檢測,包括編譯器、操作系統相關。
nginx的configure會自動檢測不同平臺的特性,神奇之處就是auto/feature的實現,在繼續向下分析之前,我們先來看看這個工具的實現原理。此工具的核心思想是,輸出一小段代表性c程序,然后設置好編譯選項,再進行編譯連接運行,再對結果進行分析。例如,如果想檢測某個庫是否存在,就在小段c程序里面調用庫里面的某個函數,再進行編譯鏈接,如果出錯,則表示庫的環境不正常,如果編譯成功,且運行正常,則庫的環境檢測正常。我們在寫nginx第三方模塊時,也常使用此工具來進行環境的檢測,所以,此工具的作用貫穿整個configure過程。
先看一小段使用例子:
ngx_feature="poll()" ngx_feature_name= ngx_feature_run=no ngx_feature_incs="#include <poll.h>" ngx_feature_path= ngx_feature_libs= ngx_feature_test="int n; struct pollfd pl;pl.fd = 0;pl.events = 0;pl.revents = 0;n = poll(&pl, 1, 0);if (n == -1) return 1" . auto/featureif [ $ngx_found = no ]; then# 如果沒有找到poll,就設置變量的值EVENT_POLL=NONE fi這段代碼在auto/unix里面實現,用來檢測當前操作系統是否支持poll函數調用。在調用auto/feature之前,需要先設置幾個輸入參數變量的值,然后結果會存在$ngx_found變量里面, 并輸出宏定義以表示支持此特性:
$ngx_feature 特性名稱 $ngx_feature_name 特性的宏定義名稱,如果特性測試成功,則會定義該宏定義 $ngx_feature_path 編譯時要查找頭文件目錄 $ngx_feature_test 要執行的測試代碼 $ngx_feature_incs 在代碼中要include的頭文件 $ngx_feature_libs 編譯時需要link的庫文件選項 $ngx_feature_run 編譯成功后,對二進制文件需要做的動作,可以是yes value bug 其它#ngx_found 如果找到,并測試成功,其值為yes,否則其值為no看看ngx_feature的關鍵代碼:
# 初始化輸出結果為no ngx_found=no#將特性名稱小寫轉換成大寫 if test -n "$ngx_feature_name"; then# 小寫轉大寫ngx_have_feature=`echo $ngx_feature_name \| tr abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ` fi# 將所有include目錄轉換成編譯選項 if test -n "$ngx_feature_path"; thenfor ngx_temp in $ngx_feature_path; dongx_feature_inc_path="$ngx_feature_inc_path -I $ngx_temp"done fi# 生成臨時的小段c程序代碼。 # $ngx_feature_incs變量是程序需要include的頭文件 # $ngx_feature_test是測試代碼 cat << END > $NGX_AUTOTEST.c#include <sys/types.h> $NGX_INCLUDE_UNISTD_H $ngx_feature_incsint main() {$ngx_feature_test;return 0; }END# 編譯命令 # 編譯之后的目標文件是 $NGX_AUTOTEST,后面會判斷這個文件是否存在來判斷是否編譯成功 ngx_test="$CC $CC_TEST_FLAGS $CC_AUX_FLAGS $ngx_feature_inc_path \-o $NGX_AUTOTEST $NGX_AUTOTEST.c $NGX_TEST_LD_OPT $ngx_feature_libs"# 執行編譯過程 # 編譯成功后,會生成$NGX_AUTOTEST命名的文件 eval "/bin/sh -c \"$ngx_test\" >> $NGX_AUTOCONF_ERR 2>&1"# 如果文件存在,則編譯成功 if [ -x $NGX_AUTOTEST ]; thencase "$ngx_feature_run" in# 需要運行來判斷是否支持特性# 測試程序能否正常執行(即程序退出后的狀態碼是否是0),如果正常退出,則特性測試成功,設置ngx_found為yes,并添加名為ngx_feature_name的宏定義,宏的值為1yes)# 如果程序正常退出,退出碼為0,則程序執行成功,我們可以在測試代碼里面手動返回非0來表示程序出錯# /bin/sh is used to intercept "Killed" or "Abort trap" messagesif /bin/sh -c $NGX_AUTOTEST >> $NGX_AUTOCONF_ERR 2>&1; thenecho " found"ngx_found=yes# 添加宏定義,宏的值為1if test -n "$ngx_feature_name"; thenhave=$ngx_have_feature . auto/havefielseecho " found but is not working"fi;;# 需要運行程序來判斷是否支持特性,如果支持,將程序標準輸出的結果作為宏的值value)# /bin/sh is used to intercept "Killed" or "Abort trap" messagesif /bin/sh -c $NGX_AUTOTEST >> $NGX_AUTOCONF_ERR 2>&1; thenecho " found"ngx_found=yes# 與yes不一樣的是,value會將程序從標準輸出里面打印出來的值,設置為ngx_feature_name宏變量的值# 在此種情況下,程序需要設置ngx_feature_name變量名cat << END >> $NGX_AUTO_CONFIG_H#ifndef $ngx_feature_name #define $ngx_feature_name `$NGX_AUTOTEST` #endifENDelseecho " found but is not working"fi;;# 與yes正好相反bug)# /bin/sh is used to intercept "Killed" or "Abort trap" messagesif /bin/sh -c $NGX_AUTOTEST >> $NGX_AUTOCONF_ERR 2>&1; thenecho " not found"elseecho " found"ngx_found=yesif test -n "$ngx_feature_name"; thenhave=$ngx_have_feature . auto/havefifi;;# 不需要運行程序,最后定義宏變量*)echo " found"ngx_found=yesif test -n "$ngx_feature_name"; thenhave=$ngx_have_feature . auto/havefi;;esac else# 編譯失敗echo " not found"# 編譯失敗,會保存信息到日志文件中echo "----------" >> $NGX_AUTOCONF_ERR# 保留編譯文件的內容cat $NGX_AUTOTEST.c >> $NGX_AUTOCONF_ERRecho "----------" >> $NGX_AUTOCONF_ERR# 保留編譯文件的選項echo $ngx_test >> $NGX_AUTOCONF_ERRecho "----------" >> $NGX_AUTOCONF_ERR fi# 最后刪除生成的臨時文件 rm $NGX_AUTOTEST*在了解了工具auto/feature后,繼續我們的主流程,auto/cc/conf的代碼就很好理解了,這一步主要是檢測編譯器,并設置編譯器相關的選項。它先調用auto/cc/name來得到編譯器的名稱,然后根據編譯器選擇執行不同的編譯器相關的文件如gcc執行auto/cc/gcc來設置編譯器相關的一些選項。
這個工具用來檢測是頭文件是否支持。需要檢測的頭文件放在$ngx_include里面,如果支持,則$ngx_found變量的值為yes,并且會產生NGX_HAVE_{ngx_include}的宏定義。
生成頭文件的宏定義。生成的定義放在objs/ngx_auto_headers.h里面:
#ifndef NGX_HAVE_UNISTD_H #define NGX_HAVE_UNISTD_H 1 #endif #ifndef NGX_HAVE_INTTYPES_H #define NGX_HAVE_INTTYPES_H 1 #endif #ifndef NGX_HAVE_LIMITS_H #define NGX_HAVE_LIMITS_H 1 #endif #ifndef NGX_HAVE_SYS_FILIO_H #define NGX_HAVE_SYS_FILIO_H 1 #endif #ifndef NGX_HAVE_SYS_PARAM_H #define NGX_HAVE_SYS_PARAM_H 1 #endif針對不同的操作系統平臺特性的檢測,并針對不同的操作系統,設置不同的CORE_INCS、CORE_DEPS、CORE_SRCS變量。nginx跨平臺的支持就是在這個地方體現出來的。
針對unix體系的通用配置或系統調用的檢測,如poll等事件處理系統調用的檢測等。
該腳本根據不同的條件,輸出不同的模塊列表,最后輸出的模塊列表的文件在objs/ngx_modules.c:
#include <ngx_config.h> #include <ngx_core.h>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_emp_server_module;...ngx_module_t *ngx_modules[] = {&ngx_core_module,&ngx_errlog_module,&ngx_conf_module,&ngx_emp_server_module,...NULL };這個文件會決定所有模塊的順序,這會直接影響到最后的功能,下一小節我們將討論模塊間的順序。這個文件會加載我們的第三方模塊,這也是我們值得關注的地方:
if test -n "$NGX_ADDONS"; thenecho configuring additional modulesfor ngx_addon_dir in $NGX_ADDONSdoecho "adding module in $ngx_addon_dir"if test -f $ngx_addon_dir/config; then# 執行第三方模塊的配置. $ngx_addon_dir/configecho " + $ngx_addon_name was configured"elseecho "$0: error: no $ngx_addon_dir/config was found"exit 1fidone fi這段代碼比較簡單,確實現了nginx很強大的擴展性,加載第三方模塊。$ngx_addon_dir變量是在configure執行時,命令行參數–add-module加入的,它是一個目錄列表,每一個目錄,表示一個第三方模塊。從代碼中,我們可以看到,它就是針對每一個第三方模塊執行其目錄下的config文件。于是我們可以在config文件里面執行我們自己的檢測邏輯,比如檢測庫依賴,添加編譯選項等。
該文件會針對nginx編譯所需要的基礎庫的檢測,比如rewrite模塊需要的PCRE庫的檢測支持。
模塊編譯順序
上一節中,提到過,nginx模塊的順序很重要,會直接影響到程序的功能。而且,nginx和部分模塊,也有著自己特定的順序要求,比如ngx_http_write_filter_module模塊一定要在filter模塊的最后一步執行。想查看模塊的執行順序,可以在objs/ngx_modules.c這個文件中找到,這個文件在configure之后生成,上一節中,我們看過這個文件里面的內容。
下面是一個ngx_modules.c文件的示例:
ngx_module_t *ngx_modules[] = {// 全局core模塊&ngx_core_module,&ngx_errlog_module,&ngx_conf_module,&ngx_emp_server_module,&ngx_emp_server_core_module,// event模塊&ngx_events_module,&ngx_event_core_module,&ngx_kqueue_module,// 正則模塊&ngx_regex_module,// http模塊&ngx_http_module,&ngx_http_core_module,&ngx_http_log_module,&ngx_http_upstream_module,// http handler模塊&ngx_http_static_module,&ngx_http_autoindex_module,&ngx_http_index_module,&ngx_http_auth_basic_module,&ngx_http_access_module,&ngx_http_limit_conn_module,&ngx_http_limit_req_module,&ngx_http_geo_module,&ngx_http_map_module,&ngx_http_split_clients_module,&ngx_http_referer_module,&ngx_http_rewrite_module,&ngx_http_proxy_module,&ngx_http_fastcgi_module,&ngx_http_uwsgi_module,&ngx_http_scgi_module,&ngx_http_memcached_module,&ngx_http_empty_gif_module,&ngx_http_browser_module,&ngx_http_upstream_ip_hash_module,&ngx_http_upstream_keepalive_module,//此處是第三方handler模塊// http filter模塊&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_ssi_filter_module,&ngx_http_charset_filter_module,&ngx_http_userid_filter_module,&ngx_http_headers_filter_module,// 第三方filter模塊&ngx_http_copy_filter_module,&ngx_http_range_body_filter_module,&ngx_http_not_modified_filter_module,NULL };http handler模塊與http filter模塊的順序很重要,這里我們主要關注一下這兩類模塊。
http handler模塊,在后面的章節里面會講到多階段請求的處理鏈。對于content phase之前的handler,同一個階段的handler,模塊是順序執行的。比如上面的示例代碼中,ngx_http_auth_basic_module與ngx_http_access_module這兩個模塊都是在access phase階段,由于ngx_http_auth_basic_module在前面,所以會先執行。由于content phase只會有一個執行,所以不存在順序問題。另外,我們加載的第三方handler模塊永遠是在最后執行。
http filter模塊,filter模塊會將所有的filter handler排成一個倒序鏈,所以在最前面的最后執行。上面的例子中,&ngx_http_write_filter_module最后執行,ngx_http_not_modified_filter_module最先執行。注意,我們加載的第三方filter模塊是在copy_filter模塊之后,headers_filter模塊之前執行。
總結
以上是生活随笔為你收集整理的nginx架构详解(50%)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: lsof/netstat命令的一个重要作
- 下一篇: 从流程上对rtmp协议经行总结