免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
123下一页
最近访问板块 发新帖
查看: 21222 | 回复: 22
打印 上一主题 下一主题

原码分析第三期<<Nginx源代码情景分析(1)——预备知识>> [复制链接]

论坛徽章:
3
金牛座
日期:2014-06-14 22:04:062015年辞旧岁徽章
日期:2015-03-03 16:54:152015年迎新春徽章
日期:2015-03-04 09:49:45
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2011-11-22 21:45 |只看该作者 |倒序浏览
本帖最后由 duanjigang 于 2011-11-22 23:02 编辑

Nginx源代码情景分析

dreamice <jiangjunyong@gmail.com>
2011-11-21



第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 各大WEB服务器市场占有率情况

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源代码的目录树结构:

图 2 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安装好的目录,可以看到如下图所示的目录树结构:

图 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进行内存分配,因为在这里程序还没有创建内存池,而后续的初始化工作,可能出现未知的错误,那么,该处初始化的错误数组就可以派上用场了。

1.jpg (64 KB, 下载次数: )

1.jpg

2.jpg (28.26 KB, 下载次数: )

2.jpg

3.jpg (35.7 KB, 下载次数: )

3.jpg

论坛徽章:
3
金牛座
日期:2014-06-14 22:04:062015年辞旧岁徽章
日期:2015-03-03 16:54:152015年迎新春徽章
日期:2015-03-04 09:49:45
2 [报告]
发表于 2011-11-22 21:47 |只看该作者
本帖最后由 dreamice 于 2011-11-23 20:49 编辑

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;
666
/* Nginx的启动选项,只要带了参数的,都以“-”开始,因此此处主要检查参数的合法性,如果不合法,就不在做后续检查,直接返回。这个地方指针也用得比较巧妙,*p++检查当前p指向的字符,然后指针下移。*/
667         if (*p++ != '-') {
668             ngx_log_stderr(0, "invalid option: \"%s\"", argv);
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;
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;
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;
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;
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”,所以只有循环才能解析完整个启动命令参数值。

论坛徽章:
0
3 [报告]
发表于 2011-11-22 23:04 |只看该作者
支持!

论坛徽章:
0
4 [报告]
发表于 2011-11-23 00:06 |只看该作者
占位

论坛徽章:
3
金牛座
日期:2014-06-14 22:04:062015年辞旧岁徽章
日期:2015-03-03 16:54:152015年迎新春徽章
日期:2015-03-04 09:49:45
5 [报告]
发表于 2011-11-23 09:12 |只看该作者
占位
把握今天 发表于 2011-11-23 00:06



    希望参与进来一起分析!

论坛徽章:
3
金牛座
日期:2014-06-14 22:04:062015年辞旧岁徽章
日期:2015-03-03 16:54:152015年迎新春徽章
日期:2015-03-04 09:49:45
6 [报告]
发表于 2011-11-23 20:49 |只看该作者
2楼今天有更新!

论坛徽章:
1
双子座
日期:2013-11-06 17:18:01
7 [报告]
发表于 2011-12-02 11:00 |只看该作者
我才知道 原来Nginx是这么小{:3_198:}tar包700K

论坛徽章:
3
金牛座
日期:2014-06-14 22:04:062015年辞旧岁徽章
日期:2015-03-03 16:54:152015年迎新春徽章
日期:2015-03-04 09:49:45
8 [报告]
发表于 2011-12-02 11:46 |只看该作者
我才知道 原来Nginx是这么小tar包700K
seufy88 发表于 2011-12-02 11:00


短小精干

论坛徽章:
0
9 [报告]
发表于 2011-12-05 07:23 |只看该作者
我来了。
我还以为不分析了呢。
这几天在看 多线程编程 | 算法 | 离散数学。

论坛徽章:
0
10 [报告]
发表于 2011-12-05 07:24 |只看该作者
本帖最后由 wangzhen11aaa 于 2011-12-05 07:28 编辑

Lz 分析很漂亮。抽时间拜读。
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP