Nginx 情景分析
来源:互联网 发布:淘宝产品类别食品 编辑:程序博客网 时间:2024/04/30 04:50
http://blog.chinaunix.net/uid-7201775-id-3033127.html
第1章 预备知识 1.1 Nginx简介
Nginx(Engine X)是一款高性能的HTTP、反向代理与负载均衡均衡服务器,同时也支持IMAP/POP3/SMPT代理服务器。Nginx的原作者是俄罗斯人Igor,他将源代码以类BSD许可证的形式发布。Nginx发布以来,因为其稳定性,丰富的功能集成,示例配置文件和低系统资源消耗而闻名。目前国内各大门户网站及相关应用越来越多的部署了Nginx,如sina,网易,淘宝,腾讯等。因此,越来越多WEB服务器应用开发者、WEB高性能研究者以及WEB应用安全研究者都投入了对Nginx的研究。甚至很多人开始语言因为Nginx的高性能、低资源消耗级稳定性的优势,有逐步抢夺Apache对WEB服务器市场占有量的趋势。
下图是各大WEB服务器自1995至2011年的市场占有率情况:
1.2 必备基础知识
由于Nginx的代码主要由C语言,部分嵌入式汇编及脚本语言组成。因此,分析Nginx源代码,必须具备扎实的C语言基础,最好能能够读懂嵌入式汇编语言,对shell脚本语言也需要有一定的掌握。
作为一个高性能的WEB服务器,分析Nginx的源代码,你还需要具备HTTP协议的基础知识,另外需要掌握一定的网络知识,尤其是socket编程,对TCP/IP协议有一定的了解。本情景分析主要是分析Linux的版本,因此,你还需要掌握Linux编程的相关知识(强烈推荐阅读《UNIX环境高级编程》一书)。另外,最好对系统及软件程序架构有一定的了解,这样可以更深入的理解Nginx的模块化构成。
1.3 Nginx的目录结构本情景分析以当前Nginx的最新版本1.1.7为参考,其他版本其主要的原理和核心代码差异不大。
以下是Nginx源代码的目录树结构:
Src为核心源代码目录,其中主要的几个目录及包含的源代码简介如下:
core : 该目录存放核心基础模块的代码,也是Nginx服务的入口
http : HTTP协议处理模块的代码,Nginx作为WEB服务器和代理服务器运行时的核心模块
mail : Mail处理模块的代码,Nginx作为pop3/imap/smtp代理服务器运行时的核心模块
event : Nginx 自身对事件处理逻辑的封装
os : Nginx对各个平台抽象逻辑的封装
misc : nginx 的一些utils,定义了test和profiler的一些外围模块的逻辑
其他几个目录:
auto:系统执行./configure时,所依赖的一些自动化脚本。
conf:Nginx相关运行时调用的配置文件模版
objs:编译生成的目标文件存放目录
man:帮助文档
html:默认的访问文件存放目录
1.4 基础数据类型在Nginx源码中,定义了很多数据类型,对原始的基础数据类型进行了封装。其中,一些基础的数据类型,在平时的编程中很少用到,这里特别说明一下。
在 core/ngx_config.h 目录里面定义了以下几个基本的数据类型的映射:
typedef intptr_t ngx_int_t;
typedef uintptr_t ngx_uint_t;
typedef intptr_t ngx_flag_t;
在linux系统中,这几个数据类型在/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
其他的数据类型,均以以上几个基础数据类型为参考,进行其他复杂的数据结构的封装定义。
由于Nginx在编程时,很多地方都自己封装了一套数据结构,带有自身的特色,这里就不一一进行说明,在后续的情景分析中,再将逐步进行深入分析。
第2章 Nginx启动与执行流程 2.1 Nginx启动
“工欲善其事必先利其器”。毛主席说,理论实践相结合,分析研究源代码,也不例外。我们首先搭建一个Nginx的环境,以源码编译安装,然后从配置深入了解配置文件,启动参数,最后结合代码中熟悉的main()函数开始,走向一条合理的分析线路。Nginx的安装在这里就不赘述。
我们进入Nginx安装好的目录,可以看到如下图所示的目录树结构:
图 SEQ 图 \* ARABIC 3 Nginx安装完成后的目录结构树
如上图所示,最重要的是nginx.conf这个文件,里面包含了nginx配置的各项参数。我们在这里先不深入分析其内容,而从启动开始。默认安装后,不需要进行配置也可以按默认项启动。
执行“nginx -?”看看帮助,我们发现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
默认情况下,在nginx安装后的目录执行./sbin/nginx即可启动,默认端口是80,通过浏览器,我们可以实现对nginx web服务器的访问。
2.2 Nginx执行流程和普通的应用程序一样,Nginx程序的执行入口函数仍旧从main()函数开始。Main()函数位于src/core/nginx.c中。Nginx启动执行主要调用的函数概况如下:
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函数调用到函数,其中可能包括一些分支才能调用到的函数,也在这里一并顺序罗列。我们现对这个调用流程有一个整体的印象,然后在深入代码,分析每个函数,并逐步分析完这个流程执行过程所做的事情。
下面我们开始正式的源代码情景分析:
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函数启动最开始的执行片段,具体的数据类型我们到函数中进行分析,因此,main执行开始涉及到的变量类型暂不进行分析。第一个函数ngx_debug_init(),这个函数主要是作为初始化debug用的,nginx中的debug,主要是对内存池分配管理方面的debug,因为作为一个应用程序,最容易出现bug的地方也是内存管理这块。在Linux版本中,这个函数只是一个空定义(src/os/unix/ngx_linux_config.h)。
#define ngx_debug_init()
2.2.1 ngx_strerror_init函数分析我们来到程序的第208行,ngx_strerror_init(),该函数的定义在文件src/os/unix/ngx_errno.c中。该函数主要初始化系统中错误编号对应的含义,这样初始化中进行对应的好处是,当出现错误,不用再去调用strerror()函数来获取错误原因,而直接可以根据错误编号找到对应的错误原因,可以提高运行时的执行效率。从这里可以看到,nginx开发者的别有用心,对于微小的性能提升也毫不放过。
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性质的文件,只有在源码安装nginx时,执行了./configure后,才能生成这个文件。
171 #ifndef NGX_SYS_NERR
172 #define NGX_SYS_NERR 132
173 #endif
在Linux系统中有132个错误编码。我们可以写一个简单的小程序来测试系统中的错误编码对应的说明:
#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;
}
具体执行结果请读者运行查看,在这里不再赘述。
同样在第57行中,sizeof(ngx_str_t)这是对nginx自己定义的字符串结构的字节长度计算。我们来分析一下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自身对字符串的表示,进行了“长度—内容”这样的方式,字符串在初始化时就进行了长度的计算记录,这样就方便了后续在使用过程中,不必重复计算字符串长度。同样是很细微的提升效率的表现啊!
接着回到对ngx_strerror_init的分析,第59行,malloc为全局的ngx_sys_errlist分配内存,这是一个全局静态变量,指向错误编码及对应字符串说明值数组的起始地址。从64至76行,完成了数组中,每一个值对应的err字符串及其长度的初始化工作。这部分也是该函数的核心工作部分。在这里nginx并没有使用其内存池,而是使用率默认的malloc进行内存分配,因为在这里程序还没有创建内存池,而后续的初始化工作,可能出现未知的错误,那么,该处初始化的错误数组就可以派上用场了。
2.2.2 ngx_get_options()ngx_get_options函数主要是解析Nginx的启动参数,Nginx的启动选项在2.1节中已经说明。
657 static ngx_int_t
658 ngx_get_options(int argc, char *const *argv)
659 {
660 u_char *p;
661 ngx_int_t i;
662
/* 这个地方特别注意,argc是从1开始检查解析的,我们知道nginx的默认启动,可以不带任何参数,所以,如果是默认第一次启动nginx,就不用进入for循环,而直接返回 */
663 for (i = 1; i < argc; i++) {
664
665 p = (u_char *) argv[i];
666
/* Nginx的启动选项,只要带了参数的,都以“-”开始,因此此处主要检查参数的合法性,如果不合法,就不在做后续检查,直接返回。这个地方指针也用得比较巧妙,*p++检查当前p指向的字符,然后指针下移。*/
667 if (*p++ != '-') {
668 ngx_log_stderr(0, "invalid option: \"%s\"", argv[i]);
669 return NGX_ERROR;
670 }
671
672 while (*p) {
673
674 switch (*p++) {
675
/* 如果是-?或者-h,则既要显示版本信息,又要显示帮助 */
676 case '?':
677 case 'h':
678 ngx_show_version = 1;
679 ngx_show_help = 1;
680 break;
681
/* 如果是执行 –v,则显示版本信息 */
682 case 'v':
683 ngx_show_version = 1;
684 break;
685
/* 如果执行-V,则显示版本信息,并显示相关配置信息,主要包括编译时的gcc版本,启用了哪些编译选项等 */
686 case 'V':
687 ngx_show_version = 1;
688 ngx_show_configure = 1;
689 break;
690
/* 如果执行的是-t,则用于test nginx的配置是否有语法错误,如果有错误则会提示,没有错误会提示syntax ok和successful 字样,这个跟apache类似。*/
691 case 't':
692 ngx_test_config = 1;
693 break;
694
/* -q是quiet模式,主要是在配置测试过程中,避免非错误信息的输出 */
695 case 'q':
696 ngx_quiet_mode = 1;
697 break;
698
/* -p主要是指明nginx启动时的配置目录,这对于重新配置nginx目录时有用 */
699 case 'p':
700 if (*p) {
701 ngx_prefix = p;
702 goto next;
703 }
704
705 if (argv[++i]) {
706 ngx_prefix = (u_char *) argv[i];
707 goto next;
708 }
709
710 ngx_log_stderr(0, "option \"-p\" requires directory name");
711 return NGX_ERROR;
712
/* -c指明启动配置文件nginx.conf的路径,当该文件存储在非标准目录的时候有用 */
713 case 'c':
714 if (*p) {
715 ngx_conf_file = p;
716 goto next;
717 }
718
719 if (argv[++i]) {
720 ngx_conf_file = (u_char *) argv[i];
721 goto next;
722 }
723
724 ngx_log_stderr(0, "option \"-c\" requires file name");
725 return NGX_ERROR;
726
/* -g 指明设置配置文件的全局指令,如:nginx -g "pid /var/run/nginx.pid; worker_processes `sysctl -n hw.ncpu`;",多个选项之间以分号分开 */
727 case 'g':
728 if (*p) {
729 ngx_conf_params = p;
730 goto next;
731 }
732
733 if (argv[++i]) {
734 ngx_conf_params = (u_char *) argv[i];
735 goto next;
736 }
737
738 ngx_log_stderr(0, "option \"-g\" requires parameter");
739 return NGX_ERROR;
740
/* -s是信号处理选项,主要可以处理stop, quit, reopen, reload 这几个作用的信号,其中,stop为停止运行,quit为退出,reopen为重新打开,reload为重新读配置文件。信号都是有master进程处理的,关于master和worker进程,在后续章节中介绍 */
741 case 's':
/* 接下来的if-else主要是将全局变量ngx_signal指向信号值,以方便在后续的处理中,来判断处理具体的信号 */
742 if (*p) {
743 ngx_signal = (char *) p;
744
745 } else if (argv[++i]) {
746 ngx_signal = argv[i];
747
748 } else {
749 ngx_log_stderr(0, "option \"-s\" requires parameter");
750 return NGX_ERROR;
751 }
752
753 if (ngx_strcmp(ngx_signal, "stop") == 0
754 || ngx_strcmp(ngx_signal, "quit") == 0
755 || ngx_strcmp(ngx_signal, "reopen") == 0
756 || ngx_strcmp(ngx_signal, "reload") == 0)
757 {
758 ngx_process = NGX_PROCESS_SIGNALLER;
759 goto next;
760 }
761
762 ngx_log_stderr(0, "invalid option: \"-s %s\"", ngx_signal);
763 return NGX_ERROR;
764
765 default:
766 ngx_log_stderr(0, "invalid option: \"%c\"", *(p - 1));
767 return NGX_ERROR;
768 }
769 }
770
771 next:
772
773 continue;
774 }
775
776 return NGX_OK;
777 }
ngx_get_options函数体之所以是一个while循环,是因为nginx可以一次传递多个参数,比如“nginx –V –c file”,所以只有循环才能解析完整个启动命令参数值。
2.2.3 ngx_time_init58 void
59 ngx_time_init(void)
60 {
61 ngx_cached_err_log_time.len = sizeof("1970/09/28 12:00:00") - 1;
62 ngx_cached_http_time.len = sizeof("Mon, 28 Sep 1970 06:00:00 GMT") - 1;
63 ngx_cached_http_log_time.len = sizeof("28/Sep/1970:12:00:00 +0600") - 1;
64 ngx_cached_http_log_iso8601.len = sizeof("1970-09-28T12:00:00+06:00") - 1;
65
66 ngx_cached_time = &cached_time[0];
67
68 ngx_time_update();
69 }
在上述代码片段中,ngx_cached_err_log_time,ngx_cached_http_time,ngx_cached_http_log_time,ngx_cached_http_log_iso8601均为ngx_str_t类型的,用于记录错误日志时间,http缓存时间,http缓存log时间级iso8061时间,初始化过程中,先计算该时间表示的字符串的长度,这样可以省却在用到的时候再进行计算。ngx_cached_time是nginx时间类型的数据结构,他是volatile类型的,即防止编译器优化,每次都要从内存中读取,而不是用缓存值。
15 typedef struct {
16 time_t sec;
17 ngx_uint_t msec;
18 ngx_int_t gmtoff;
19 } ngx_time_t;
ngx_time_update()函数用于更新系统时间。
72 void
73 ngx_time_update(void)
74 {
75 u_char *p0, *p1, *p2, *p3;
76 ngx_tm_t tm, gmt;
77 time_t sec;
78 ngx_uint_t msec;
79 ngx_time_t *tp;
80 struct timeval tv;
81
82 if (!ngx_trylock(&ngx_time_lock)) {
83 return;
84 }
我们先看该函数(行73—84)片段的实现,ngx_trylock()获取时间更新的互斥锁,避免进程或线程间并发更新系统时间。对于ngx_trylock()我们可以跟踪其实现原型:
306 #define ngx_trylock(lock) (*(lock) == 0 && ngx_atomic_cmp_set(lock, 0, 1))
这里判断*lock为0,表示没有被枷锁,并对其进行原子操作加锁ngx_atomic_cmp_set,设置其值为1。ngx_atomic_cmp_set的实现如下(x86 unix系统):
14 /*
15 * "cmpxchgl r, [m]":
16 *
17 * if (eax == [m]) {
18 * zf = 1;
19 * [m] = r;
20 * } else {
21 * zf = 0;
22 * eax = [m];
23 * }
24 *
25 *
26 * The "r" means the general register.
27 * The "=a" and "a" are the %eax register.
28 * Although we can return result in any register, we use "a" because it is
29 * used in cmpxchgl anyway. The result is actually in %al but not in %eax,
30 * however, as the code is inlined gcc can test %al as well as %eax,
31 * and icc adds "movzbl %al, %eax" by itself.
32 *
33 * The "cc" means that flags were changed.
34 */
35
36 static ngx_inline ngx_atomic_uint_t
37 ngx_atomic_cmp_set(ngx_atomic_t *lock, ngx_atomic_uint_t old,
38 ngx_atomic_uint_t set)
39 {
40 u_char res;
41
42 __asm__ volatile (
43
44 NGX_SMP_LOCK
45 " cmpxchgl %3, %1; "
46 " sete %0; "
47
48 : "=a" (res) : "m" (*lock), "a" (old), "r" (set) : "cc", "memory");
49
50 return res;
51 }
该函数通过嵌入式汇编的方式,进行加锁并原子设定lock的值为1。这里不展开对函数实现的细节分析。我们进一步分析该函数的后续实现部分。这里假设我们通过ngx_trylock函数获得了更新系统时间的锁,继续往后走。
85
86 ngx_gettimeofday(&tv); /* 此处为系统标准函数,获取系统时间,存储到tv变量中 */
87
88 sec = tv.tv_sec;
89 msec = tv.tv_usec / 1000;
/* 以上两行把tv获取的值,转换为秒和毫秒,并在接下来的一行代码中统一换算为秒*/
90
91 ngx_current_msec = (ngx_msec_t) sec * 1000 + msec;
92
93 tp = &cached_time[slot];
94
95 if (tp->sec == sec) {
96 tp->msec = msec;
97 ngx_unlock(&ngx_time_lock);
98 return;
99 }
/* 如果系统缓存的时间秒和当前更新的秒值未发生变化,则只需更新毫秒值,然后返回,否则认为系统长时间未更新时间,继续往后执行 */
100
101 if (slot == NGX_TIME_SLOTS - 1) {
102 slot = 0;
103 } else {
104 slot++;
105 }
106
107 tp = &cached_time[slot];
108
109 tp->sec = sec;
110 tp->msec = msec;
111
112 ngx_gmtime(sec, &gmt);
/* ngx_gmtime 将时间换算为天、小时、分、秒具体实现比较简单,这里不再深入分析*/
113
114
115 p0 = &cached_http_time[slot][0];
116
117 (void) ngx_sprintf(p0, "%s, %02d %s %4d %02d:%02d:%02d GMT",
118 week[gmt.ngx_tm_wday], gmt.ngx_tm_mday,
119 months[gmt.ngx_tm_mon - 1], gmt.ngx_tm_year,
120 gmt.ngx_tm_hour, gmt.ngx_tm_min, gmt.ngx_tm_sec);
121
122 #if (NGX_HAVE_GETTIMEZONE)
123
/* 这里指需要计算时区 */
124 tp->gmtoff = ngx_gettimezone();
125 ngx_gmtime(sec + tp->gmtoff * 60, &tm);
126
127 #elif (NGX_HAVE_GMTOFF)
128
129 ngx_localtime(sec, &tm);
130 cached_gmtoff = (ngx_int_t) (tm.ngx_tm_gmtoff / 60);
131 tp->gmtoff = cached_gmtoff;
132
133 #else
134
/* 直接计算本地系统时间 */
135 ngx_localtime(sec, &tm);
136 cached_gmtoff = ngx_timezone(tm.ngx_tm_isdst);
137 tp->gmtoff = cached_gmtoff;
138
139 #endif
140
141
142 p1 = &cached_err_log_time[slot][0];
143
144 (void) ngx_sprintf(p1, "%4d/%02d/%02d %02d:%02d:%02d",
145 tm.ngx_tm_year, tm.ngx_tm_mon,
146 tm.ngx_tm_mday, tm.ngx_tm_hour,
147 tm.ngx_tm_min, tm.ngx_tm_sec);
148
149
150 p2 = &cached_http_log_time[slot][0];
151
152 (void) ngx_sprintf(p2, "%02d/%s/%d:%02d:%02d:%02d %c%02d%02d",
153 tm.ngx_tm_mday, months[tm.ngx_tm_mon - 1],
154 tm.ngx_tm_year, tm.ngx_tm_hour,
155 tm.ngx_tm_min, tm.ngx_tm_sec,
156 tp->gmtoff < 0 ? '-' : '+',
157 ngx_abs(tp->gmtoff / 60), ngx_abs(tp->gmtoff % 60));
158
159 p3 = &cached_http_log_iso8601[slot][0];
160
161 (void) ngx_sprintf(p3, "%4d-%02d-%02dT%02d:%02d:%02d%c%02d:%02d",
162 tm.ngx_tm_year, tm.ngx_tm_mon,
163 tm.ngx_tm_mday, tm.ngx_tm_hour,
164 tm.ngx_tm_min, tm.ngx_tm_sec,
165 tp->gmtoff < 0 ? '-' : '+',
166 ngx_abs(tp->gmtoff / 60), ngx_abs(tp->gmtoff % 60));
167
/* 上述的几个printf 完全按照时间的需求格式,将系统时间存储到对应的变量中,这里我们看到,已经不再有长度的计算之类的。*/
168
169 ngx_memory_barrier(); /* 一个应用层设置内存屏障的函数,表示上述片段已经计算完毕,需要完成内存的同步,然后在后续的几步操作中,实现对初始化最初的几个全局变量的赋值操作。这里再次看到,没有字符串长度的计算,nginx通过初始化一次长度计算从而一劳永逸,而不用每次计算时再去纠结字符串的长度问题。这一点比起apache来说,确实优化不少。*/
170
171 ngx_cached_time = tp;
172 ngx_cached_http_time.data = p0;
173 ngx_cached_err_log_time.data = p1;
174 ngx_cached_http_log_time.data = p2;
175 ngx_cached_http_log_iso8601.data = p3;
176
177 ngx_unlock(&ngx_time_lock); /* 释放锁 */
178 }
2.2.4 ngx_regex_init()269 #if (NGX_PCRE)
270 ngx_regex_init();
271 #endif
如果启用了PCRE功能,则进行正则表达式的初始化工作。Nginx中的pcre主要是用来支持URL Rewrite的,URL Rewrite主要是为了满足代理模式下,对请求访问的URL地址进行rewrite操作,来实现定向访问。
18 void
19 ngx_regex_init(void)
20 {
21 pcre_malloc = ngx_regex_malloc;
22 pcre_free = ngx_regex_free;
23 }
Pcre的初始化操作,主要是初始化处理pcre正则时的内存分配和释放,因此其赋值操作也仅是两个内存操作。关于内存管理,在后续独立的内存管理章节进行分析讨论。
2.2.5 ngx_log_init()由于log是相对独立的一块,在这里我们对Nginx的log部分,进行细致的分析研究。
先看一下log的结构体:
typedef struct ngx_log_s ngx_log_t;
47 struct ngx_log_s {
48 ngx_uint_t log_level;
49 ngx_open_file_t *file;
50
51 ngx_atomic_uint_t connection;
52
53 ngx_log_handler_pt handler;
54 void *data;
55
56 /*
57 * we declare "action" as "char *" because the actions are usually
58 * the static strings and in the "u_char *" case we have to override
59 * their types all the time
60 */
61
62 char *action;
63 };
第一个字段是log_level,说明日志记录的级别,在nginx中,与其他的web服务器一样,也包含了0—8个相对标准的级别:
15 #define NGX_LOG_STDERR 0
16 #define NGX_LOG_EMERG 1
17 #define NGX_LOG_ALERT 2
18 #define NGX_LOG_CRIT 3
19 #define NGX_LOG_ERR 4
20 #define NGX_LOG_WARN 5
21 #define NGX_LOG_NOTICE 6
22 #define NGX_LOG_INFO 7
23 #define NGX_LOG_DEBUG 8
在正式使用中,建议配置在3级以下,因为级别数字越大,产生的告警日志越多。日志越多,对于调试分析非常有用,但在正式使用过程中,日志数量产生太多,会写磁盘IO,对性能会造成极大的影响。
File是log日志的记录文件标识符,在nginx中文件描述符同样进行了特定的封装:
18 typedef struct ngx_open_file_s ngx_open_file_t;
89 struct ngx_open_file_s {
90 ngx_fd_t fd; /* 标准IO文件描述符 */
91 ngx_str_t name; /* 文件名称 */
92
93 u_char *buffer; /* 文件buffer */
94 u_char *pos; /* 指示文件中的位置 */
95 u_char *last;
96
97 #if 0
98 /* e.g. append mode, error_log */
99 ngx_uint_t flags;
100 /* e.g. reopen db file */
101 ngx_uint_t (*handler)(void *data, ngx_open_file_t *file);
102 void *data;
103 #endif
104 };
重新回到ngx_log_init()函数实现,其他几个为分析的参数后续再做说明。
264 ngx_log_t *
265 ngx_log_init(u_char *prefix)
266 {
267 u_char *p, *name;
268 size_t nlen, plen;
269
270 ngx_log.file = &ngx_log_file; /*此处初始化log中的file字段存储全局变量ngx_log_file的地址*/
271 ngx_log.log_level = NGX_LOG_NOTICE;
272
273 name = (u_char *) NGX_ERROR_LOG_PATH;
这里名字初始化为error日志文件路径,默认定义为(objs/ngx_auto_config.h):
321 #ifndef NGX_ERROR_LOG_PATH
322 #define NGX_ERROR_LOG_PATH "logs/error.log"
323 #endif
274
275 /*
276 * we use ngx_strlen() here since BCC warns about
277 * condition is always false and unreachable code
278 */
279
280 nlen = ngx_strlen(name);
281
282 if (nlen == 0) {
283 ngx_log_file.fd = ngx_stderr;
284 return &ngx_log;
285 }
286
287 p = NULL;
288
289 #if (NGX_WIN32)
290 if (name[1] != ':') {
291 #else
292 if (name[0] != '/') {
293 #endif
294
295 if (prefix) {
296 plen = ngx_strlen(prefix);
297
298 } else {
299 #ifdef NGX_PREFIX
300 prefix = (u_char *) NGX_PREFIX;
301 plen = ngx_strlen(prefix);
302 #else
303 plen = 0;
304 #endif
305 }
306
307 if (plen) {
308 name = malloc(plen + nlen + 2);
309 if (name == NULL) {
310 return NULL;
311 }
312
313 p = ngx_cpymem(name, prefix, plen);
314
315 if (!ngx_path_separator(*(p - 1))) {
316 *p++ = '/';
317 }
318
319 ngx_cpystrn(p, (u_char *) NGX_ERROR_LOG_PATH, nlen + 1);
320
321 p = name;
322 }
323 }
在上述这个片段中,主要分配内存,来存储log文件名,prefix为指定的路径前缀。初始化log文件的路径名称后,后续就要打开log文件,进行必要的初始化操作了。
324
325 ngx_log_file.fd = ngx_open_file(name, NGX_FILE_APPEND,
326 NGX_FILE_CREATE_OR_OPEN,
327 NGX_FILE_DEFAULT_ACCESS);
看看src/os/unix/ngx_files.h中对ngx_open_file的定义:
57 #define ngx_open_file(name, mode, create, access) \
58 open((const char *) name, mode|create|O_BINARY, access)
实际上就是调用了标准的open函数。
在src/os/unix/ngx_files.h文件中
75 #define NGX_FILE_APPEND O_WRONLY|O_APPEND
72 #define NGX_FILE_CREATE_OR_OPEN O_CREAT
78 #define NGX_FILE_DEFAULT_ACCESS 0644
可以看到文件是以只写方式打开的,并执行追加的方式,如果文件不存在,则先创建该文件,并赋予文件0644的权限,创建者和超级用户才具有读写权限,其他用户和组用户只有读权限。这里要特别注意这一点,普通用户是没办法改写nginx的日志的,另外文件是初始化时候打开的初始化的,不要试图在运行过程中以超级用户权限删除文件,认为还会继续有日志文件产生记录。这个和apache是类似的。
328
329 if (ngx_log_file.fd == NGX_INVALID_FILE) {
330 ngx_log_stderr(ngx_errno,
331 "[alert] could not open error log file: "
332 ngx_open_file_n " \"%s\" failed", name);
333 #if (NGX_WIN32)
334 ngx_event_log(ngx_errno,
335 "could not open error log file: "
336 ngx_open_file_n " \"%s\" failed", name);
337 #endif
338
339 ngx_log_file.fd = ngx_stderr;
340 }
如果文件创建出错,将标准错误赋给log文件描述符。
341
342 if (p) {
343 ngx_free(p);
344 }
之前处理文件名这一串,都是为了打开文件做准备的,完毕后,它的使命也结束了,释放存储的内存。并返回,nginx的log便初始化完毕。
345
346 return &ngx_log;
347 }
Nginx的ssl部分暂不进行分析,因此其初始化暂时略过。由于后续的初始化部分涉及到很多模块内容,顺着初始化这条线,后续部分暂不在本章节中叙述。初始化中的下一个片段是内存池的初始化部分,这部分相对较复杂,我们单独列一章来进行分析。见第三章。
第3章 Nginx内存管理
内存管理是各个WEB服务器都相继实现了的独立功能,作为一个满足高性能的WEB服务器,面对各种请求和应答处理流程,必然涉及到内存以及连接的分配与管理,如果完全采用标准的malloc/free函数接口实现内存管理,频繁的调用必然引起性能的低效。Nginx也不例外,采用了短小精干的方式,实现了其特有的内存管理方式。通过这部分的分析学习,希望我们也能达到融会贯通的目的,不仅能深入理解Nginx的内存管理机制,在实际应用中也能学到其独特的机制,并加以运用。对于开发Nginx模块来说,就更要熟悉其内存管理的相关接口。
内存相关的操作主要在文件 os/unix/ngx_alloc.{h,c} 和 core/ngx_palloc.{h,c} 中实现。先分析内存管理的几个主要数据结构:
14 typedef struct ngx_pool_s ngx_pool_t;
48 typedef struct {
49 u_char *last;
50 u_char *end;
51 ngx_pool_t *next;
52 ngx_uint_t failed;
53 } ngx_pool_data_t;
Last:当前内存分配结束位置,即下一段可分配内存的起始位置;
End:内存池的结束位置;
Next:链接到下一个内存池
Failded:记录内存分配不能满足需求的失败次数;
ngx_pool_data_t;结构用来维护内存池的数据块,供用户分配之用。
54
55
56 struct ngx_pool_s {
57 ngx_pool_data_t d;
58 size_t max;
59 ngx_pool_t *current;
60 ngx_chain_t *chain;
61 ngx_pool_large_t *large;
62 ngx_pool_cleanup_t *cleanup;
63 ngx_log_t *log;
64 };
d:数据块
max:数据块大小,小块内存的最大值
current:指向本内存池
chain:这里可以挂一个链结构
large:指向大块内存分配,nginx中,大块内存分配直接采用标准系统接口malloc
cleanup:析构函数,挂载内存释放时需要清理资源的一些必要操作;
log:内存分配相关的日志记录
再看看大块数据分配的结构体:
42 struct ngx_pool_large_s {
43 ngx_pool_large_t *next;
44 void *alloc;
45 };
这个组织比较简单就是一个链表。
下图展示了内存的管理组织结构:
图 SEQ 图 \* ARABIC 4 内存管理结构
接下来,我们深入分析内存管理的主要函数。
(1)ngx_create_pool(size_t size, ngx_log_t *log)
size:分配内存的大小
log:用于日志记录
下面是该函数的具体实现
15 ngx_pool_t *
16 ngx_create_pool(size_t size, ngx_log_t *log)
17 {
18 ngx_pool_t *p;
19
20 p = ngx_memalign(NGX_POOL_ALIGNMENT, size, log);
21 if (p == NULL) {
22 return NULL;
23 }
24
25 p->d.last = (u_char *) p + sizeof(ngx_pool_t);
26 p->d.end = (u_char *) p + size;
27 p->d.next = NULL;
28 p->d.failed = 0;
29
30 size = size - sizeof(ngx_pool_t);
31 p->max = (size < NGX_MAX_ALLOC_FROM_POOL) ? size : NGX_MAX_ALLOC_FROM_POOL;
32
33 p->current = p;
34 p->chain = NULL;
35 p->large = NULL;
36 p->cleanup = NULL;
37 p->log = log;
38
39 return p;
40 }
第20行ngx_memalign()函数执行内存分配,该函数的实现在src/os/unix/ngx_alloc.c文件中(假定NGX_HAVE_POSIX_MEMALIGN被定义):
50 void *
51 ngx_memalign(size_t alignment, size_t size, ngx_log_t *log)
52 {
53 void *p;
54 int err;
55
56 err = posix_memalign(&p, alignment, size);
该函数分配以alignment为对齐的size字节的内存大小,其中p指向分配的内存块。
57
58 if (err) {
59 ngx_log_error(NGX_LOG_EMERG, log, err,
60 "posix_memalign(%uz, %uz) failed", alignment, size);
61 p = NULL;
62 }
63
64 ngx_log_debug3(NGX_LOG_DEBUG_ALLOC, log, 0,
65 "posix_memalign: %p:%uz @%uz", p, size, alignment);
66
67 return p;
68 }
从这个函数的实现体,我们可以看到p = ngx_memalign(NGX_POOL_ALIGNMENT, size, log);函数分配以NGX_POOL_ALIGNMENT字节对齐的size字节的内存,在src/core/ngx_palloc.h文件中:
23 #define NGX_POOL_ALIGNMENT 16
因此,nginx的内存池分配,是以16字节为边界对齐的。
函数的25—40行,是按照图4的所示的结构进行组织进行初始化,具体实现读者可以对照图4来加以理解。
(2)void ngx_destroy_pool(ngx_pool_t *pool)
内存池的销毁函数,pool指向需要销毁的内存池。
43 void
44 ngx_destroy_pool(ngx_pool_t *pool)
45 {
46 ngx_pool_t *p, *n;
47 ngx_pool_large_t *l;
48 ngx_pool_cleanup_t *c;
49
50 for (c = pool->cleanup; c; c = c->next) {
51 if (c->handler) {
52 ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, pool->log, 0,
53 "run cleanup: %p", c);
54 c->handler(c->data);
55 }
56 }
前面讲到,cleanup指向析构函数,用于执行相关的内存池销毁之前的清理工作,如文件的关闭等,清理函数是一个handler的函数指针挂载。因此,在这部分,对内存池中的析构函数遍历调用。
57
58 for (l = pool->large; l; l = l->next) {
59
60 ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, pool->log, 0, "free: %p", l->alloc);
61
62 if (l->alloc) {
63 ngx_free(l->alloc);
64 }
65 }
这一部分用于清理大块内存,ngx_free实际上就是标准的free函数,即大内存块就是通过malloc和free操作进行管理的。
66
67 #if (NGX_DEBUG)
68
69 /*
70 * we could allocate the pool->log from this pool
71 * so we cannot use this log while free()ing the pool
72 */
73
74 for (p = pool, n = pool->d.next; /* void */; p = n, n = n->d.next) {
75 ngx_log_debug2(NGX_LOG_DEBUG_ALLOC, pool->log, 0,
76 "free: %p, unused: %uz", p, p->d.end - p->d.last);
77
78 if (n == NULL) {
79 break;
80 }
81 }
82
只有debug模式才会执行这个片段的代码,主要是log记录,用以跟踪函数销毁时日志记录。
83 #endif
84
85 for (p = pool, n = pool->d.next; /* void */; p = n, n = n->d.next) {
86 ngx_free(p);
87
88 if (n == NULL) {
89 break;
90 }
91 }
92 }
该片段彻底销毁内存池本身。
- Nginx 情景分析
- Nginx情景分析之配置文件解析
- 《Nginx源代码情景分析系列》先收藏着
- Nginx源代码情景分析(3)——Nginx内存管理-1
- myicq情景分析
- myicq情景分析
- yaffs2源代码情景分析
- mtd 情景分析
- yaffs2源代码情景分析
- segmentation 情景分析
- yaffs2源代码情景分析
- 情景分析:安德鲁蚂蚁
- do_fork情景分析
- sudo 命令情景分析
- windows内核情景分析
- Jamendo源代码情景分析
- sudo 命令情景分析
- Retrofit情景分析
- hostname VS uname -n
- 卑微你存活太久了:伤感日志
- Javascript中===与==区别
- JAVAC 命令详解
- 王爽《汇编语言》课程设计1
- Nginx 情景分析
- JAVA和C++的区别
- ARM汇编ADR,LDR等伪指令
- BNU
- 网络应用
- AutoMapper 自动映射工具
- 快速模取幂算法
- 老板爱炒的15种员工 .
- 0.driverbase-VS2008+DDKWIZARD+WDK基于WIN7编程环境配置