nginx架构详解(50%)

nginx的下篇将会更加深入的介绍nginx的实现原理。上一章,我们了解到了如何设计一个高性能服务器,那这一章将会开始讲解,nginx是如何一步一步实现高性能服务器的。

nginx的源码目录结构(100%)

nginx的优秀除了体现在程序结构以及代码风格上,nginx的源码组织也同样简洁明了,目录结构层次结构清晰,值得我们去学习。nginx的源码目录与nginx的模块化以及功能的划分是紧密结合,这也使得我们可以很方便地找到相关功能的代码。这节先介绍nginx源码的目录结构,先对nginx的源码有一个大致的认识,下节会讲解nginx如何编译。

下面是nginx源码的目录结构:

  1. .
  2. ├── auto 自动检测系统环境以及编译相关的脚本
  3. ├── cc 关于编译器相关的编译选项的检测脚本
  4. ├── lib nginx编译所需要的一些库的检测脚本
  5. ├── os 与平台相关的一些系统参数与系统调用相关的检测
  6. └── types 与数据类型相关的一些辅助脚本
  7. ├── conf 存放默认配置文件,在make install后,会拷贝到安装目录中去
  8. ├── contrib 存放一些实用工具,如geo配置生成工具(geo2nginx.pl
  9. ├── html 存放默认的网页文件,在make install后,会拷贝到安装目录中去
  10. ├── man nginxman手册
  11. └── src 存放nginx的源代码
  12. ├── core nginx的核心源代码,包括常用数据结构的定义,以及nginx初始化运行的核心代码如main函数
  13. ├── event 对系统事件处理机制的封装,以及定时器的实现相关代码
  14. └── modules 不同事件处理方式的模块化,如selectpollepollkqueue
  15. ├── http nginx作为http服务器相关的代码
  16. └── modules 包含http的各种功能模块
  17. ├── mail nginx作为邮件代理服务器相关的代码
  18. ├── misc 一些辅助代码,测试c++头的兼容性,以及对google_perftools的支持
  19. └── os 主要是对各种不同体系统结构所提供的系统函数的封装,对外提供统一的系统调用接口

nginx的configure原理(100%)

nginx的编译旅程将从configure开始,configure脚本将根据我们输入的选项、系统环境参与来生成所需的文件(包含源文件与Makefile文件)。configure会调用一系列auto脚本来实现编译环境的初始化。

auto脚本

auto脚本由一系列脚本组成,他们有一些是实现一些通用功能由其它脚本来调用(如have),有一些则是完成一些特定的功能(如option)。脚本之间的主要执行顺序及调用关系如下图所示(由上到下,表示主流程的执行):

chapter-9-1

接下来,我们结合代码来分析下configure的原理:

1.初始化

  1. . auto/options
  2. . auto/init
  3. . auto/sources

这是configure源码开始执行的前三行,依次交由auto目录下面的option、init、sources来处理。

2.auto/options主是处理用户输入的configure选项,以及输出帮助信息等。读者可以结合nginx的源码来阅读本章内容。由于篇幅关系,这里大致列出此文件的结构:

  1. ##1. 设置选项对应的shell变量以及他们的初始值
  2. help=no
  3. NGX_PREFIX=
  4. NGX_SBIN_PATH=
  5. NGX_CONF_PREFIX=
  6. NGX_CONF_PATH=
  7. NGX_ERROR_LOG_PATH=
  8. NGX_PID_PATH=
  9. NGX_LOCK_PATH=
  10. NGX_USER=
  11. NGX_GROUP=
  12.  
  13. ...
  14.  
  15.  
  16. ## 2, 处理每一个选项值,并设置到对应的全局变量中
  17. for option
  18. do
  19. opt="$opt `echo $option | sed -e \"s/\(--[^=]*=\)\(.* .*\)/\1'\2'/\"`"
  20.  
  21. # 得到此选项的value部分
  22. case "$option" in
  23. -*=*) value=`echo "$option" | sed -e 's/[-_a-zA-Z0-9]*=//'` ;;
  24. *) value="" ;;
  25. esac
  26.  
  27. # 根据option内容进行匹配,并设置相应的选项
  28. case "$option" in
  29. --help) help=yes ;;
  30. --prefix=) NGX_PREFIX="!" ;;
  31. --prefix=*) NGX_PREFIX="$value" ;;
  32. --sbin-path=*) NGX_SBIN_PATH="$value" ;;
  33. --conf-path=*) NGX_CONF_PATH="$value" ;;
  34. --error-log-path=*) NGX_ERROR_LOG_PATH="$value";;
  35. --pid-path=*) NGX_PID_PATH="$value" ;;
  36. --lock-path=*) NGX_LOCK_PATH="$value" ;;
  37. --user=*) NGX_USER="$value" ;;
  38. --group=*) NGX_GROUP="$value" ;;
  39.  
  40. ...
  41.  
  42. *)
  43. # 没有找到的对应选项
  44. echo "$0: error: invalid option \"$option\""
  45. exit 1
  46. ;;
  47. esac
  48. done
  49.  
  50. ## 3. 对选项进行处理
  51.  
  52. # 如果有--help,则输出帮助信息
  53. if [ $help = yes ]; then
  54.  
  55. cat << END
  56.  
  57. --help print this message
  58.  
  59. --prefix=PATH set installation prefix
  60. --sbin-path=PATH set nginx binary pathname
  61. --conf-path=PATH set nginx.conf pathname
  62. --error-log-path=PATH set error log pathname
  63. --pid-path=PATH set nginx.pid pathname
  64. --lock-path=PATH set nginx.lock pathname
  65.  
  66. --user=USER set non-privileged user for
  67. worker processes
  68. --group=GROUP set non-privileged group for
  69. worker processes
  70. END
  71.  
  72. exit 1
  73. fi
  74.  
  75. # 默认文件路径
  76. NGX_CONF_PATH=${NGX_CONF_PATH:-conf/nginx.conf}
  77. NGX_CONF_PREFIX=`dirname $NGX_CONF_PATH`
  78. NGX_PID_PATH=${NGX_PID_PATH:-logs/nginx.pid}
  79. NGX_LOCK_PATH=${NGX_LOCK_PATH:-logs/nginx.lock}
  80.  
  81. ...

上面的代码中,我们选用了文件中的部分代码进行了说明。大家可结合源码再进行分析。auto/options的目的主要是处理用户选项,并由选项生成一些全局变量的值,这些值在其它文件中会用到。该文件也会输出configure的帮助信息。

3.auto/init

该文件的目录在于初始化一些临时文件的路径,检查echo的兼容性,并创建Makefile。

  1. # 生成最终执行编译的makefile文件路径
  2. NGX_MAKEFILE=$NGX_OBJS/Makefile
  3. # 动态生成nginx模块列表的路径,由于nginx的的一些模块是可以选择编译的,而且可以添加自己的模块,所以模块列表是动态生成的
  4. NGX_MODULES_C=$NGX_OBJS/ngx_modules.c
  5.  
  6. NGX_AUTO_HEADERS_H=$NGX_OBJS/ngx_auto_headers.h
  7. NGX_AUTO_CONFIG_H=$NGX_OBJS/ngx_auto_config.h
  8.  
  9. # 自动测试目录与日志输出文件
  10. NGX_AUTOTEST=$NGX_OBJS/autotest
  11. # 如果configure出错,可用来查找出错的原因
  12. NGX_AUTOCONF_ERR=$NGX_OBJS/autoconf.err
  13.  
  14. NGX_ERR=$NGX_OBJS/autoconf.err
  15. MAKEFILE=$NGX_OBJS/Makefile
  16.  
  17.  
  18. NGX_PCH=
  19. NGX_USE_PCH=
  20.  
  21.  
  22. # 检查echo是否支持-n或\c
  23.  
  24. # check the echo's "-n" option and "\c" capability
  25.  
  26. if echo "test\c" | grep c >/dev/null; then
  27.  
  28. # 不支持-c的方式,检查是否支持-n的方式
  29.  
  30. if echo -n test | grep n >/dev/null; then
  31. ngx_n=
  32. ngx_c=
  33.  
  34. else
  35. ngx_n=-n
  36. ngx_c=
  37. fi
  38.  
  39. else
  40. ngx_n=
  41. ngx_c='\c'
  42. fi
  43.  
  44. # 创建最初始的makefile文件
  45. # default表示目前编译对象
  46. # clean表示执行clean工作时,需要删除makefile文件以及objs目录
  47. # 整个过程中只会生成makefile文件以及objs目录,其它所有临时文件都在objs目录之下,所以执行clean后,整个目录还原到初始状态
  48. # 要再次执行编译,需要重新执行configure命令
  49.  
  50. # create Makefile
  51.  
  52. cat << END > Makefile
  53.  
  54. default: build
  55.  
  56. clean:
  57. rm -rf Makefile $NGX_OBJS
  58. END

4.auto/sources

该文件从文件名中就可以看出,它的主要功能是跟源文件相关的。它的主要作用是定义不同功能或系统所需要的文件的变量。根据功能,分为CORE/REGEX/EVENT/UNIX/FREEBSD/HTTP等。每一个功能将会由四个变量组成,”_MODULES”表示此功能相关的模块,最终会输出到ngx_modules.c文件中,即动态生成需要编译到nginx中的模块;”INCS”表示此功能依赖的源码目录,查找头文件的时候会用到,在编译选项中,会出现在”-I”中;”DEPS”显示指明在Makefile中需要依赖的文件名,即编译时,需要检查这些文件的更新时间;”SRCS”表示需要此功能编译需要的源文件。

拿core来说:

  1. CORE_MODULES="ngx_core_module ngx_errlog_module ngx_conf_module ngx_emp_server_module ngx_emp_server_core_module"
  2.  
  3. CORE_INCS="src/core"
  4.  
  5. CORE_DEPS="src/core/nginx.h \
  6. src/core/ngx_config.h \
  7. src/core/ngx_core.h \
  8. src/core/ngx_log.h \
  9. src/core/ngx_palloc.h \
  10. src/core/ngx_array.h \
  11. src/core/ngx_list.h \
  12. src/core/ngx_hash.h \
  13. src/core/ngx_buf.h \
  14. src/core/ngx_queue.h \
  15. src/core/ngx_string.h \
  16. src/core/ngx_parse.h \
  17. src/core/ngx_inet.h \
  18. src/core/ngx_file.h \
  19. src/core/ngx_crc.h \
  20. src/core/ngx_crc32.h \
  21. src/core/ngx_murmurhash.h \
  22. src/core/ngx_md5.h \
  23. src/core/ngx_sha1.h \
  24. src/core/ngx_rbtree.h \
  25. src/core/ngx_radix_tree.h \
  26. src/core/ngx_slab.h \
  27. src/core/ngx_times.h \
  28. src/core/ngx_shmtx.h \
  29. src/core/ngx_connection.h \
  30. src/core/ngx_cycle.h \
  31. src/core/ngx_conf_file.h \
  32. src/core/ngx_resolver.h \
  33. src/core/ngx_open_file_cache.h \
  34. src/core/nginx_emp_server.h \
  35. src/core/emp_server.h \
  36. src/core/task_thread.h \
  37. src/core/standard.h \
  38. src/core/dprint.h \
  39. src/core/ngx_crypt.h"
  40.  
  41. CORE_SRCS="src/core/nginx.c \
  42. src/core/ngx_log.c \
  43. src/core/ngx_palloc.c \
  44. src/core/ngx_array.c \
  45. src/core/ngx_list.c \
  46. src/core/ngx_hash.c \
  47. src/core/ngx_buf.c \
  48. src/core/ngx_queue.c \
  49. src/core/ngx_output_chain.c \
  50. src/core/ngx_string.c \
  51. src/core/ngx_parse.c \
  52. src/core/ngx_inet.c \
  53. src/core/ngx_file.c \
  54. src/core/ngx_crc32.c \
  55. src/core/ngx_murmurhash.c \
  56. src/core/ngx_md5.c \
  57. src/core/ngx_rbtree.c \
  58. src/core/ngx_radix_tree.c \
  59. src/core/ngx_slab.c \
  60. src/core/ngx_times.c \
  61. src/core/ngx_shmtx.c \
  62. src/core/ngx_connection.c \
  63. src/core/ngx_cycle.c \
  64. src/core/ngx_spinlock.c \
  65. src/core/ngx_cpuinfo.c \
  66. src/core/ngx_conf_file.c \
  67. src/core/ngx_resolver.c \
  68. src/core/ngx_open_file_cache.c \
  69. src/core/nginx_emp_server.c \
  70. src/core/emp_server.c \
  71. src/core/standard.c \
  72. src/core/task_thread.c \
  73. src/core/dprint.c \
  74. src/core/ngx_crypt.c"

如果我们自己写一个第三方模块,我们可能会引用到这些变量的值,或对这些变量进行修改,比如添加我们自己的模块,或添加自己的一个头文件查找目录(在第三方模块的config中),在后面,我们会看到它是如何加框第三方模块的。 在继续分析执行流程之前,我们先介绍一些工具脚本。

5.auto/have

  1. cat << END >> $NGX_AUTO_CONFIG_H
  2.  
  3. #ifndef $have
  4. #define $have 1
  5. #endif
  6.  
  7. END

从代码中,我们可以看到,这个工具的作用是,将$have变量的值,宏定义为1,并输出到auto_config文件中。通常我们通过这个工具来控制是否打开某个特性。这个工具在使用前,需要先定义宏的名称 ,即$have变量。

6.再回到configure文件中来:

  1. # NGX_DEBUG是在auto/options文件中处理的,如果有--with-debug选项,则其值是YES
  2. if [ $NGX_DEBUG = YES ]; then
  3. # 当有debug选项时,会定义NGX_DEBUG宏
  4. have=NGX_DEBUG . auto/have
  5. fi

这段代码中,可以看出,configure是如何定义一个特性的:通过宏定义,输出到config头文件中,然后在程序中可以判断这个宏是否有定义,来实现不同的特性。

configure文件中继续向下:

  1. # 编译器选项
  2. . auto/cc/conf
  3.  
  4. # 头文件支持宏定义
  5. if [ "$NGX_PLATFORM" != win32 ]; then
  6. . auto/headers
  7. fi
  8.  
  9. # 操作系统相关的配置的检测
  10. . auto/os/conf
  11.  
  12. # unix体系下的通用配置检测
  13. if [ "$NGX_PLATFORM" != win32 ]; then
  14. . auto/unix
  15. fi

configure会依次调用其它几个文件,来进行环境的检测,包括编译器、操作系统相关。

7.auto/feature

nginx的configure会自动检测不同平台的特性,神奇之处就是auto/feature的实现,在继续向下分析之前,我们先来看看这个工具的实现原理。此工具的核心思想是,输出一小段代表性c程序,然后设置好编译选项,再进行编译连接运行,再对结果进行分析。例如,如果想检测某个库是否存在,就在小段c程序里面调用库里面的某个函数,再进行编译链接,如果出错,则表示库的环境不正常,如果编译成功,且运行正常,则库的环境检测正常。我们在写nginx第三方模块时,也常使用此工具来进行环境的检测,所以,此工具的作用贯穿整个configure过程。

先看一小段使用例子:

  1. ngx_feature="poll()"
  2. ngx_feature_name=
  3. ngx_feature_run=no
  4. ngx_feature_incs="#include <poll.h>"
  5. ngx_feature_path=
  6. ngx_feature_libs=
  7. ngx_feature_test="int n; struct pollfd pl;
  8. pl.fd = 0;
  9. pl.events = 0;
  10. pl.revents = 0;
  11. n = poll(&pl, 1, 0);
  12. if (n == -1) return 1"
  13. 8.auto/feature
  14.  
  15. if [ $ngx_found = no ]; then
  16. # 如果没有找到poll,就设置变量的值
  17. EVENT_POLL=NONE
  18. fi

这段代码在auto/unix里面实现,用来检测当前操作系统是否支持poll函数调用。在调用auto/feature之前,需要先设置几个输入参数变量的值,然后结果会存在$ngx_found变量里面, 并输出宏定义以表示支持此特性:

  1. $ngx_feature 特性名称
  2. $ngx_feature_name 特性的宏定义名称,如果特性测试成功,则会定义该宏定义
  3. $ngx_feature_path 编译时要查找头文件目录
  4. $ngx_feature_test 要执行的测试代码
  5. $ngx_feature_incs 在代码中要include的头文件
  6. $ngx_feature_libs 编译时需要link的库文件选项
  7. $ngx_feature_run 编译成功后,对二进制文件需要做的动作,可以是yes value bug 其它
  8.  
  9. #ngx_found 如果找到,并测试成功,其值为yes,否则其值为no

看看ngx_feature的关键代码:

  1. # 初始化输出结果为no
  2. ngx_found=no
  3.  
  4. #将特性名称小写转换成大写
  5. if test -n "$ngx_feature_name"; then
  6. # 小写转大写
  7. ngx_have_feature=`echo $ngx_feature_name \
  8. | tr abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ`
  9. fi
  10.  
  11. # 将所有include目录转换成编译选项
  12. if test -n "$ngx_feature_path"; then
  13. for ngx_temp in $ngx_feature_path; do
  14. ngx_feature_inc_path="$ngx_feature_inc_path -I $ngx_temp"
  15. done
  16. fi
  17.  
  18.  
  19. # 生成临时的小段c程序代码。
  20. # $ngx_feature_incs变量是程序需要include的头文件
  21. # $ngx_feature_test是测试代码
  22. cat << END > $NGX_AUTOTEST.c
  23.  
  24. #include <sys/types.h>
  25. $NGX_INCLUDE_UNISTD_H
  26. $ngx_feature_incs
  27.  
  28. int main() {
  29. $ngx_feature_test;
  30. return 0;
  31. }
  32.  
  33. END
  34.  
  35. # 编译命令
  36. # 编译之后的目标文件是 $NGX_AUTOTEST,后面会判断这个文件是否存在来判断是否编译成功
  37. ngx_test="$CC $CC_TEST_FLAGS $CC_AUX_FLAGS $ngx_feature_inc_path \
  38. -o $NGX_AUTOTEST $NGX_AUTOTEST.c $NGX_TEST_LD_OPT $ngx_feature_libs"
  39.  
  40. # 执行编译过程
  41. # 编译成功后,会生成$NGX_AUTOTEST命名的文件
  42. eval "/bin/sh -c \"$ngx_test\" >> $NGX_AUTOCONF_ERR 2>&1"
  43.  
  44. # 如果文件存在,则编译成功
  45. if [ -x $NGX_AUTOTEST ]; then
  46.  
  47. case "$ngx_feature_run" in
  48.  
  49. # 需要运行来判断是否支持特性
  50. # 测试程序能否正常执行(即程序退出后的状态码是否是0),如果正常退出,则特性测试成功,设置ngx_found为yes,并添加名为ngx_feature_name的宏定义,宏的值为1
  51. yes)
  52. # 如果程序正常退出,退出码为0,则程序执行成功,我们可以在测试代码里面手动返回非0来表示程序出错
  53. # /bin/sh is used to intercept "Killed" or "Abort trap" messages
  54. if /bin/sh -c $NGX_AUTOTEST >> $NGX_AUTOCONF_ERR 2>&1; then
  55. echo " found"
  56. ngx_found=yes
  57.  
  58. # 添加宏定义,宏的值为1
  59. if test -n "$ngx_feature_name"; then
  60. have=$ngx_have_feature . auto/have
  61. fi
  62.  
  63. else
  64. echo " found but is not working"
  65. fi
  66. ;;
  67.  
  68. # 需要运行程序来判断是否支持特性,如果支持,将程序标准输出的结果作为宏的值
  69. value)
  70. # /bin/sh is used to intercept "Killed" or "Abort trap" messages
  71. if /bin/sh -c $NGX_AUTOTEST >> $NGX_AUTOCONF_ERR 2>&1; then
  72. echo " found"
  73. ngx_found=yes
  74.  
  75. # 与yes不一样的是,value会将程序从标准输出里面打印出来的值,设置为ngx_feature_name宏变量的值
  76. # 在此种情况下,程序需要设置ngx_feature_name变量名
  77. cat << END >> $NGX_AUTO_CONFIG_H
  78.  
  79. #ifndef $ngx_feature_name
  80. #define $ngx_feature_name `$NGX_AUTOTEST`
  81. #endif
  82.  
  83. END
  84. else
  85. echo " found but is not working"
  86. fi
  87. ;;
  88.  
  89. # 与yes正好相反
  90. bug)
  91. # /bin/sh is used to intercept "Killed" or "Abort trap" messages
  92. if /bin/sh -c $NGX_AUTOTEST >> $NGX_AUTOCONF_ERR 2>&1; then
  93. echo " not found"
  94.  
  95. else
  96. echo " found"
  97. ngx_found=yes
  98.  
  99. if test -n "$ngx_feature_name"; then
  100. have=$ngx_have_feature . auto/have
  101. fi
  102. fi
  103. ;;
  104.  
  105. # 不需要运行程序,最后定义宏变量
  106. *)
  107. echo " found"
  108. ngx_found=yes
  109.  
  110. if test -n "$ngx_feature_name"; then
  111. have=$ngx_have_feature . auto/have
  112. fi
  113. ;;
  114.  
  115. esac
  116. else
  117. # 编译失败
  118. echo " not found"
  119.  
  120. # 编译失败,会保存信息到日志文件中
  121. echo "----------" >> $NGX_AUTOCONF_ERR
  122. # 保留编译文件的内容
  123. cat $NGX_AUTOTEST.c >> $NGX_AUTOCONF_ERR
  124. echo "----------" >> $NGX_AUTOCONF_ERR
  125. # 保留编译文件的选项
  126. echo $ngx_test >> $NGX_AUTOCONF_ERR
  127. echo "----------" >> $NGX_AUTOCONF_ERR
  128. fi
  129.  
  130. # 最后删除生成的临时文件
  131. rm $NGX_AUTOTEST*

9.auto/cc/conf

在了解了工具auto/feature后,继续我们的主流程,auto/cc/conf的代码就很好理解了,这一步主要是检测编译器,并设置编译器相关的选项。它先调用auto/cc/name来得到编译器的名称,然后根据编译器选择执行不同的编译器相关的文件如gcc执行auto/cc/gcc来设置编译器相关的一些选项。

10.auto/include

这个工具用来检测是头文件是否支持。需要检测的头文件放在$ngxinclude里面,如果支持,则$ngx_found变量的值为yes,并且会产生NGX_HAVE{ngx_include}的宏定义。

11.auto/headers

生成头文件的宏定义。生成的定义放在objs/ngx_auto_headers.h里面:

  1. #ifndef NGX_HAVE_UNISTD_H
  2. #define NGX_HAVE_UNISTD_H 1
  3. #endif
  4.  
  5.  
  6. #ifndef NGX_HAVE_INTTYPES_H
  7. #define NGX_HAVE_INTTYPES_H 1
  8. #endif
  9.  
  10.  
  11. #ifndef NGX_HAVE_LIMITS_H
  12. #define NGX_HAVE_LIMITS_H 1
  13. #endif
  14.  
  15.  
  16. #ifndef NGX_HAVE_SYS_FILIO_H
  17. #define NGX_HAVE_SYS_FILIO_H 1
  18. #endif
  19.  
  20.  
  21. #ifndef NGX_HAVE_SYS_PARAM_H
  22. #define NGX_HAVE_SYS_PARAM_H 1
  23. #endif
  24.  

12.auto/os/conf

针对不同的操作系统平台特性的检测,并针对不同的操作系统,设置不同的CORE_INCS、CORE_DEPS、CORE_SRCS变量。nginx跨平台的支持就是在这个地方体现出来的。

13.auto/unix

针对unix体系的通用配置或系统调用的检测,如poll等事件处理系统调用的检测等。

14.回到configure里面

  1. # 生成模块列表
  2. . auto/modules
  3. # 配置库的依赖
  4. . auto/lib/conf

15.auto/modules

该脚本根据不同的条件,输出不同的模块列表,最后输出的模块列表的文件在objs/ngx_modules.c:

  1. #include <ngx_config.h>
  2. #include <ngx_core.h>
  3.  
  4.  
  5. extern ngx_module_t ngx_core_module;
  6. extern ngx_module_t ngx_errlog_module;
  7. extern ngx_module_t ngx_conf_module;
  8. extern ngx_module_t ngx_emp_server_module;
  9.  
  10. ...
  11.  
  12.  
  13. ngx_module_t *ngx_modules[] = {
  14. &ngx_core_module,
  15. &ngx_errlog_module,
  16. &ngx_conf_module,
  17. &ngx_emp_server_module,
  18. ...
  19. NULL
  20. };

这个文件会决定所有模块的顺序,这会直接影响到最后的功能,下一小节我们将讨论模块间的顺序。这个文件会加载我们的第三方模块,这也是我们值得关注的地方:

  1. if test -n "$NGX_ADDONS"; then
  2.  
  3. echo configuring additional modules
  4.  
  5. for ngx_addon_dir in $NGX_ADDONS
  6. do
  7. echo "adding module in $ngx_addon_dir"
  8.  
  9. if test -f $ngx_addon_dir/config; then
  10. # 执行第三方模块的配置
  11. . $ngx_addon_dir/config
  12.  
  13. echo " + $ngx_addon_name was configured"
  14.  
  15. else
  16. echo "$0: error: no $ngx_addon_dir/config was found"
  17. exit 1
  18. fi
  19. done
  20. fi

这段代码比较简单,确实现了nginx很强大的扩展性,加载第三方模块。$ngx_addon_dir变量是在configure执行时,命令行参数–add-module加入的,它是一个目录列表,每一个目录,表示一个第三方模块。从代码中,我们可以看到,它就是针对每一个第三方模块执行其目录下的config文件。于是我们可以在config文件里面执行我们自己的检测逻辑,比如检测库依赖,添加编译选项等。

16.auto/lib/conf

该文件会针对nginx编译所需要的基础库的检测,比如rewrite模块需要的PCRE库的检测支持。

17.configure接下来定义一些宏常量,主要是是文件路径方面的:

  1. case ".$NGX_PREFIX" in
  2. .)
  3. NGX_PREFIX=${NGX_PREFIX:-/usr/local/nginx}
  4. have=NGX_PREFIX value="\"$NGX_PREFIX/\"" . auto/define
  5. ;;
  6.  
  7. .!)
  8. NGX_PREFIX=
  9. ;;
  10.  
  11. *)
  12. have=NGX_PREFIX value="\"$NGX_PREFIX/\"" . auto/define
  13. ;;
  14. esac
  15.  
  16. if [ ".$NGX_CONF_PREFIX" != "." ]; then
  17. have=NGX_CONF_PREFIX value="\"$NGX_CONF_PREFIX/\"" . auto/define
  18. fi
  19.  
  20. have=NGX_SBIN_PATH value="\"$NGX_SBIN_PATH\"" . auto/define
  21. have=NGX_CONF_PATH value="\"$NGX_CONF_PATH\"" . auto/define
  22. have=NGX_PID_PATH value="\"$NGX_PID_PATH\"" . auto/define
  23. have=NGX_LOCK_PATH value="\"$NGX_LOCK_PATH\"" . auto/define
  24. have=NGX_ERROR_LOG_PATH value="\"$NGX_ERROR_LOG_PATH\"" . auto/define
  25.  
  26. have=NGX_HTTP_LOG_PATH value="\"$NGX_HTTP_LOG_PATH\"" . auto/define
  27. have=NGX_HTTP_CLIENT_TEMP_PATH value="\"$NGX_HTTP_CLIENT_TEMP_PATH\""
  28. . auto/define
  29. have=NGX_HTTP_PROXY_TEMP_PATH value="\"$NGX_HTTP_PROXY_TEMP_PATH\""
  30. . auto/define
  31. have=NGX_HTTP_FASTCGI_TEMP_PATH value="\"$NGX_HTTP_FASTCGI_TEMP_PATH\""
  32. . auto/define
  33. have=NGX_HTTP_UWSGI_TEMP_PATH value="\"$NGX_HTTP_UWSGI_TEMP_PATH\""
  34. . auto/define
  35. have=NGX_HTTP_SCGI_TEMP_PATH value="\"$NGX_HTTP_SCGI_TEMP_PATH\""
  36. . auto/define

18.configure最后的工作,生成编译安装的makefile

  1. # 生成objs/makefile文件
  2. . auto/make
  3.  
  4. # 生成关于库的编译选项到makefile文件
  5. . auto/lib/make
  6. # 生成与安装相关的makefile文件内容,并生成最外层的makefile文件
  7. . auto/install
  8.  
  9. # STUB
  10. . auto/stubs
  11.  
  12. have=NGX_USER value="\"$NGX_USER\"" . auto/define
  13. have=NGX_GROUP value="\"$NGX_GROUP\"" . auto/define
  14.  
  15. # 编译的最后阶段,汇总信息
  16. . auto/summary

模块编译顺序

上一节中,提到过,nginx模块的顺序很重要,会直接影响到程序的功能。而且,nginx和部分模块,也有着自己特定的顺序要求,比如ngx_http_write_filter_module模块一定要在filter模块的最后一步执行。想查看模块的执行顺序,可以在objs/ngx_modules.c这个文件中找到,这个文件在configure之后生成,上一节中,我们看过这个文件里面的内容。

下面是一个ngx_modules.c文件的示例:

  1. ngx_module_t *ngx_modules[] = {
  2. // 全局core模块
  3. &ngx_core_module,
  4. &ngx_errlog_module,
  5. &ngx_conf_module,
  6. &ngx_emp_server_module,
  7. &ngx_emp_server_core_module,
  8.  
  9. // event模块
  10. &ngx_events_module,
  11. &ngx_event_core_module,
  12. &ngx_kqueue_module,
  13.  
  14. // 正则模块
  15. &ngx_regex_module,
  16.  
  17. // http模块
  18. &ngx_http_module,
  19. &ngx_http_core_module,
  20. &ngx_http_log_module,
  21. &ngx_http_upstream_module,
  22.  
  23. // http handler模块
  24. &ngx_http_static_module,
  25. &ngx_http_autoindex_module,
  26. &ngx_http_index_module,
  27. &ngx_http_auth_basic_module,
  28. &ngx_http_access_module,
  29. &ngx_http_limit_conn_module,
  30. &ngx_http_limit_req_module,
  31. &ngx_http_geo_module,
  32. &ngx_http_map_module,
  33. &ngx_http_split_clients_module,
  34. &ngx_http_referer_module,
  35. &ngx_http_rewrite_module,
  36. &ngx_http_proxy_module,
  37. &ngx_http_fastcgi_module,
  38. &ngx_http_uwsgi_module,
  39. &ngx_http_scgi_module,
  40. &ngx_http_memcached_module,
  41. &ngx_http_empty_gif_module,
  42. &ngx_http_browser_module,
  43. &ngx_http_upstream_ip_hash_module,
  44. &ngx_http_upstream_keepalive_module,
  45. //此处是第三方handler模块
  46.  
  47. // http filter模块
  48. &ngx_http_write_filter_module,
  49. &ngx_http_header_filter_module,
  50. &ngx_http_chunked_filter_module,
  51. &ngx_http_range_header_filter_module,
  52. &ngx_http_gzip_filter_module,
  53. &ngx_http_postpone_filter_module,
  54. &ngx_http_ssi_filter_module,
  55. &ngx_http_charset_filter_module,
  56. &ngx_http_userid_filter_module,
  57. &ngx_http_headers_filter_module,
  58. // 第三方filter模块
  59. &ngx_http_copy_filter_module,
  60. &ngx_http_range_body_filter_module,
  61. &ngx_http_not_modified_filter_module,
  62. NULL
  63. };

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的事件机制

event框架及非阻塞模型

定时器实现

信号处理

惊群问题

nginx的进程机制

master进程

worker进程

进程间通讯