- 論壇徽章:
- 3
|
本帖最后由 duanjigang 于 2011-11-22 23:02 編輯
Nginx源代碼情景分析
dreamice <jiangjunyong@gmail.com>
2011-11-21
第1章 預(yù)備知識
1.1 Nginx簡介
Nginx(Engine X)是一款高性能的HTTP、反向代理與負(fù)載均衡均衡服務(wù)器,同時也支持IMAP/POP3/SMPT代理服務(wù)器。Nginx的原作者是俄羅斯人Igor,他將源代碼以類BSD許可證的形式發(fā)布。Nginx發(fā)布以來,因為其穩(wěn)定性,豐富的功能集成,示例配置文件和低系統(tǒng)資源消耗而聞名。目前國內(nèi)各大門戶網(wǎng)站及相關(guān)應(yīng)用越來越多的部署了Nginx,如sina,網(wǎng)易,淘寶,騰訊等。因此,越來越多WEB服務(wù)器應(yīng)用開發(fā)者、WEB高性能研究者以及WEB應(yīng)用安全研究者都投入了對Nginx的研究。甚至很多人開始語言因為Nginx的高性能、低資源消耗級穩(wěn)定性的優(yōu)勢,有逐步搶奪Apache對WEB服務(wù)器市場占有量的趨勢。
下圖是各大WEB服務(wù)器自1995至2011年的市場占有率情況:
1.jpg (64 KB, 下載次數(shù): 86)
下載附件
2011-11-22 21:43 上傳
圖 1 各大WEB服務(wù)器市場占有率情況
1.2 必備基礎(chǔ)知識
由于Nginx的代碼主要由C語言,部分嵌入式匯編及腳本語言組成。因此,分析Nginx源代碼,必須具備扎實的C語言基礎(chǔ),最好能能夠讀懂嵌入式匯編語言,對shell腳本語言也需要有一定的掌握。
作為一個高性能的WEB服務(wù)器,分析Nginx的源代碼,你還需要具備HTTP協(xié)議的基礎(chǔ)知識,另外需要掌握一定的網(wǎng)絡(luò)知識,尤其是socket編程,對TCP/IP協(xié)議有一定的了解。本情景分析主要是分析Linux的版本,因此,你還需要掌握Linux編程的相關(guān)知識(強(qiáng)烈推薦閱讀《UNIX環(huán)境高級編程》一書)。另外,最好對系統(tǒng)及軟件程序架構(gòu)有一定的了解,這樣可以更深入的理解Nginx的模塊化構(gòu)成。
1.3 Nginx的目錄結(jié)構(gòu)
本情景分析以當(dāng)前Nginx的最新版本1.1.7為參考,其他版本其主要的原理和核心代碼差異不大。
以下是Nginx源代碼的目錄樹結(jié)構(gòu):
2.jpg (28.26 KB, 下載次數(shù): 84)
下載附件
2011-11-22 21:44 上傳
圖 2 Nginx源碼目錄樹
Src為核心源代碼目錄,其中主要的幾個目錄及包含的源代碼簡介如下:
core : 該目錄存放核心基礎(chǔ)模塊的代碼,也是Nginx服務(wù)的入口
http : HTTP協(xié)議處理模塊的代碼,Nginx作為WEB服務(wù)器和代理服務(wù)器運(yùn)行時的核心模塊
mail : Mail處理模塊的代碼,Nginx作為pop3/imap/smtp代理服務(wù)器運(yùn)行時的核心模塊
event : Nginx 自身對事件處理邏輯的封裝
os : Nginx對各個平臺抽象邏輯的封裝
misc : nginx 的一些utils,定義了test和profiler的一些外圍模塊的邏輯
其他幾個目錄:
auto:系統(tǒng)執(zhí)行./configure時,所依賴的一些自動化腳本。
conf:Nginx相關(guān)運(yùn)行時調(diào)用的配置文件模版
objs:編譯生成的目標(biāo)文件存放目錄
man:幫助文檔
html:默認(rèn)的訪問文件存放目錄
1.4 基礎(chǔ)數(shù)據(jù)類型
在Nginx源碼中,定義了很多數(shù)據(jù)類型,對原始的基礎(chǔ)數(shù)據(jù)類型進(jìn)行了封裝。其中,一些基礎(chǔ)的數(shù)據(jù)類型,在平時的編程中很少用到,這里特別說明一下。
在 core/ngx_config.h 目錄里面定義了以下幾個基本的數(shù)據(jù)類型的映射:
typedef intptr_t ngx_int_t;
typedef uintptr_t ngx_uint_t;
typedef intptr_t ngx_flag_t;
在linux系統(tǒng)中,這幾個數(shù)據(jù)類型在/usr/include/stdint.h的定義為:
/* Types for `void *' pointers. */
#if __WORDSIZE == 64
# ifndef __intptr_t_defined
typedef long int intptr_t;
# define __intptr_t_defined
# endif
typedef unsigned long int uintptr_t;
#else
# ifndef __intptr_t_defined
typedef int intptr_t;
# define __intptr_t_defined
# endif
typedef unsigned int uintptr_t;
#endif
其他的數(shù)據(jù)類型,均以以上幾個基礎(chǔ)數(shù)據(jù)類型為參考,進(jìn)行其他復(fù)雜的數(shù)據(jù)結(jié)構(gòu)的封裝定義。
由于Nginx在編程時,很多地方都自己封裝了一套數(shù)據(jù)結(jié)構(gòu),帶有自身的特色,這里就不一一進(jìn)行說明,在后續(xù)的情景分析中,再將逐步進(jìn)行深入分析。
第2章 Nginx啟動與執(zhí)行流程
2.1 Nginx啟動
“工欲善其事必先利其器”。毛主席說,理論實踐相結(jié)合,分析研究源代碼,也不例外。我們首先搭建一個Nginx的環(huán)境,以源碼編譯安裝,然后從配置深入了解配置文件,啟動參數(shù),最后結(jié)合代碼中熟悉的main()函數(shù)開始,走向一條合理的分析線路。Nginx的安裝在這里就不贅述。
我們進(jìn)入Nginx安裝好的目錄,可以看到如下圖所示的目錄樹結(jié)構(gòu):
3.jpg (35.7 KB, 下載次數(shù): 78)
下載附件
2011-11-22 21:45 上傳
圖 3 Nginx安裝完成后的目錄結(jié)構(gòu)樹
如上圖所示,最重要的是nginx.conf這個文件,里面包含了nginx配置的各項參數(shù)。我們在這里先不深入分析其內(nèi)容,而從啟動開始。默認(rèn)安裝后,不需要進(jìn)行配置也可以按默認(rèn)項啟動。
執(zhí)行“nginx -?”看看幫助,我們發(fā)現(xiàn)Nginx有以下啟動選項。
root@debian:/nginx# ./sbin/nginx -?
nginx: nginx version: nginx/1.0.5
nginx: Usage: nginx [-?hvVtq] [-s signal] [-c filename] [-p prefix] [-g directives]
Options:
-?,-h : this help
-v : show version and exit
-V : show version and configure options then exit
-t : test configuration and exit
-q : suppress non-error messages during configuration testing
-s signal : send signal to a master process: stop, quit, reopen, reload
-p prefix : set prefix path (default: /nginx/)
-c filename : set configuration file (default: conf/nginx.conf)
-g directives : set global directives out of configuration file
默認(rèn)情況下,在nginx安裝后的目錄執(zhí)行./sbin/nginx即可啟動,默認(rèn)端口是80,通過瀏覽器,我們可以實現(xiàn)對nginx web服務(wù)器的訪問。
2.2 Nginx執(zhí)行流程
和普通的應(yīng)用程序一樣,Nginx程序的執(zhí)行入口函數(shù)仍舊從main()函數(shù)開始。Main()函數(shù)位于src/core/nginx.c中。Nginx啟動執(zhí)行主要調(diào)用的函數(shù)概況如下:
main()
--ngx_debug_init()
--ngx_strerror_init()
--ngx_get_options(argc, argv)
--ngx_time_init()
--ngx_regex_init()
--ngx_log_init()
--ngx_ssl_init()
--ngx_memzero()
--ngx_create_pool()
--ngx_save_argv()
--ngx_process_options()
--ngx_os_init()
--ngx_crc32_table_init()
--ngx_add_inherited_sockets()
--ngx_init_cycle()
--ngx_signal_process()
--ngx_os_status()
--ngx_get_conf()
--ngx_init_signals()
--ngx_daemon()
--ngx_create_pidfile()
----ngx_single_process_cycle()
----ngx_master_process_cycle()
以上是main函數(shù)調(diào)用到函數(shù),其中可能包括一些分支才能調(diào)用到的函數(shù),也在這里一并順序羅列。我們現(xiàn)對這個調(diào)用流程有一個整體的印象,然后在深入代碼,分析每個函數(shù),并逐步分析完這個流程執(zhí)行過程所做的事情。
下面我們開始正式的源代碼情景分析:
198 int ngx_cdecl
199 main(int argc, char *const *argv)
200 {
201 ngx_int_t i;
202 ngx_log_t *log;
203 ngx_cycle_t *cycle, init_cycle;
204 ngx_core_conf_t *ccf;
205
206 ngx_debug_init();
207
208 if (ngx_strerror_init() != NGX_OK) {
209 return 1;
210 }
211
212 if (ngx_get_options(argc, argv) != NGX_OK) {
213 return 1;
214 }
以上是main函數(shù)啟動最開始的執(zhí)行片段,具體的數(shù)據(jù)類型我們到函數(shù)中進(jìn)行分析,因此,main執(zhí)行開始涉及到的變量類型暫不進(jìn)行分析。第一個函數(shù)ngx_debug_init(),這個函數(shù)主要是作為初始化debug用的,nginx中的debug,主要是對內(nèi)存池分配管理方面的debug,因為作為一個應(yīng)用程序,最容易出現(xiàn)bug的地方也是內(nèi)存管理這塊。在Linux版本中,這個函數(shù)只是一個空定義(src/os/unix/ngx_linux_config.h)。
#define ngx_debug_init()
2.2.1 ngx_strerror_init函數(shù)分析
我們來到程序的第208行,ngx_strerror_init(),該函數(shù)的定義在文件src/os/unix/ngx_errno.c中。該函數(shù)主要初始化系統(tǒng)中錯誤編號對應(yīng)的含義,這樣初始化中進(jìn)行對應(yīng)的好處是,當(dāng)出現(xiàn)錯誤,不用再去調(diào)用strerror()函數(shù)來獲取錯誤原因,而直接可以根據(jù)錯誤編號找到對應(yīng)的錯誤原因,可以提高運(yùn)行時的執(zhí)行效率。從這里可以看到,nginx開發(fā)者的別有用心,對于微小的性能提升也毫不放過。
44 ngx_uint_t
45 ngx_strerror_init(void)
46 {
47 char *msg;
48 u_char *p;
49 size_t len;
50 ngx_err_t err;
51
52 /*
53 * ngx_strerror() is not ready to work at this stage, therefore,
54 * malloc() is used and possible errors are logged using strerror().
55 */
56
57 len = NGX_SYS_NERR * sizeof(ngx_str_t);
58
59 ngx_sys_errlist = malloc(len);
60 if (ngx_sys_errlist == NULL) {
61 goto failed;
62 }
63
64 for (err = 0; err < NGX_SYS_NERR; err++) {
65 msg = strerror(err);
66 len = ngx_strlen(msg);
67
68 p = malloc(len);
69 if (p == NULL) {
70 goto failed;
71 }
72
73 ngx_memcpy(p, msg, len);
74 ngx_sys_errlist[err].len = len;
75 ngx_sys_errlist[err].data = p;
76 }
77
78 return NGX_OK;
79
80 failed:
81
82 err = errno;
83 ngx_log_stderr(0, "malloc(%uz) failed (%d: %s)", len, err, strerror(err));
84
85 return NGX_ERROR;
86 }
第57行中,NGX_SYS_NERR定義在objs/ngx_auto_config.h文件中,特別注意,這是一個auto性質(zhì)的文件,只有在源碼安裝nginx時,執(zhí)行了./configure后,才能生成這個文件。
171 #ifndef NGX_SYS_NERR
172 #define NGX_SYS_NERR 132
173 #endif
在Linux系統(tǒng)中有132個錯誤編碼。我們可以寫一個簡單的小程序來測試系統(tǒng)中的錯誤編碼對應(yīng)的說明:
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#define ERRO_NUM 132
int main()
{
int i;
for(i = 0; i < ERRO_NUM; i++)
printf("%d:%s\n", i, strerror(i));
return 0;
}
具體執(zhí)行結(jié)果請讀者運(yùn)行查看,在這里不再贅述。
同樣在第57行中,sizeof(ngx_str_t)這是對nginx自己定義的字符串結(jié)構(gòu)的字節(jié)長度計算。我們來分析一下nginx的字符串表示形式。
在src/core/ngx_string.h文件中:
15 typedef struct {
16 size_t len;
17 u_char *data;
18 } ngx_str_t;
19
20
21 typedef struct {
22 ngx_str_t key;
23 ngx_str_t value;
24 } ngx_keyval_t;
Nginx自身對字符串的表示,進(jìn)行了“長度—內(nèi)容”這樣的方式,字符串在初始化時就進(jìn)行了長度的計算記錄,這樣就方便了后續(xù)在使用過程中,不必重復(fù)計算字符串長度。同樣是很細(xì)微的提升效率的表現(xiàn)啊!
接著回到對ngx_strerror_init的分析,第59行,malloc為全局的ngx_sys_errlist分配內(nèi)存,這是一個全局靜態(tài)變量,指向錯誤編碼及對應(yīng)字符串說明值數(shù)組的起始地址。從64至76行,完成了數(shù)組中,每一個值對應(yīng)的err字符串及其長度的初始化工作。這部分也是該函數(shù)的核心工作部分。在這里nginx并沒有使用其內(nèi)存池,而是使用率默認(rèn)的malloc進(jìn)行內(nèi)存分配,因為在這里程序還沒有創(chuàng)建內(nèi)存池,而后續(xù)的初始化工作,可能出現(xiàn)未知的錯誤,那么,該處初始化的錯誤數(shù)組就可以派上用場了。 |
-
1.jpg
(64 KB, 下載次數(shù): )
下載附件
2011-11-22 21:43 上傳
-
2.jpg
(28.26 KB, 下載次數(shù): )
下載附件
2011-11-22 21:44 上傳
-
3.jpg
(35.7 KB, 下載次數(shù): )
下載附件
2011-11-22 21:45 上傳
|