ljh第2课1nginx源码分析.docx
《ljh第2课1nginx源码分析.docx》由会员分享,可在线阅读,更多相关《ljh第2课1nginx源码分析.docx(110页珍藏版)》请在冰豆网上搜索。
ljh第2课1nginx源码分析
nginx源码分析
2011-10-13ljh第四课
注意:
nginx的三个部分:
核心、事件模型/并发模型、应用模块,此文只介绍到核心、事件模型/并发模型。
应用模块包括HTTP和MAIL并没有分析。
目录
nginx源码分析
(1)- 缘起1
nginx源码分析
(2)- 概览2
nginx源码分析(3)- 自动脚本6
nginx源码分析(4)-方法
(1)6
nginx源码分析(5)-方法
(2)7
nginx源码分析(6)-模块化
(1)8
nginx源码分析(7)-模块化
(2)12
nginx源码分析(8)-模块化(3)15
nginx源码分析(9)-模块化(4)18
nginx源码分析(10)-启动过程分析27
nginx源码分析(11)-进程启动分析
(1)32
nginx源码分析(12)-进程启动分析
(2)44
nginx源码分析(13)-运维与配置
(1)50
nginx源码分析(14)-运维与配置
(2)58
nginx源码分析(15)-模块分析
(1)60
nginx源码分析(16)-模块分析
(2)70
nginx源码分析(17)-模块分析(3)72
nginx源码分析(18)-基础设施
(1)83
nginx源码分析(19)-方法(3)91
nginx源码分析
(1)- 缘起
nginx是一个开源的高性能web服务器系统,事件驱动的请求处理方式和极其苛刻的资源使用方式,使得nginx成为名副其实的高性能服务器。
nginx的源码质量也相当高,作者“家酿”了许多代码,自造了不少轮子,诸如内存池、缓冲区、字符串、链表、红黑树等经典数据结构,事件驱动模型,http解析,各种子处理模块,甚至是自动编译脚本都是作者根据自己的理解写出来的,也正因为这样,才使得nginx比其他的web服务器更加高效。
nginx的代码相当精巧和紧凑,虽然全部代码仅有10万行,但功能毫不逊色于几十万行的apache。
不过各个部分之间耦合的比较厉害,很难把其中某个部分的实现拆出来使用。
对于这样一个中大型的复杂系统源码进行分析,是有一定的难度的,刚开始也很难找到下手的入口,所以做这样的事情就必须首先明确目标和计划。
最初决定做这件事情是为了给自己一些挑战,让生活更有意思。
但看了几天之后,觉得这件事情不该这么简单看待,这里面有太多吸引人的东西了,值得有计划的系统学习和分析。
首先这个系统中几乎涵盖了实现高性能服务器的各种必杀技,epoll、kqueue、master-workers、pool、buffer......,也涵盖了很多web服务开发方面的技术,ssi、ssl、proxy、gzip、regex、loadbalancing、reconfiguration、hotcodeswapping......,还有一些常用的精巧的数据结构实现,所有的东西很主流;其次是一流的代码组织结构和干净简洁的代码风格,尤其是整个系统的命名恰到好处,可读性相当高,很kiss,这种风格值得学习和模仿;第三是通过阅读源码可以感受到作者严谨的作风和卓越的能力,可以给自己增加动力,树立榜样的力量。
另一方面,要达到这些目标难度很高,必须要制定详细的计划和采取一定有效的方法。
对于这么大的一个系统,想一口气知晓全部的细节是不可能的,并且nginx各个部分的实现之间关系紧密,不可能做到窥一斑而知全身,合适的做法似乎应该是从main开始,先了解nginx的启动过程的顺序,然后进行问题分解,再逐个重点分析每一个重要的部分。
对每个理解的关键部分进行详细的记录和整理也是很重要的,这也是这个源码分析日志系列所要完成的任务。
为了更深刻的理解代码实现的关键,修改代码和写一些测试用例是不可避免的,这就需要搭建一个方便调试的环境,这也比较容易,因为使用的linux系统本身就是一个天然的开发调试环境。
个人的能力是有限的,幸运的是互联网上还有一帮同好也在孜孜不倦的做着同样的事情,与他们的交流会帮助少走一些弯路,也会互相促进,更深入和准确的理解源码的本实。
开始一次愉快的旅行,go!
nginx源码分析
(2)- 概览
源码分析是一个逐步取精的过程,最开始是一个大概了解的过程,各种认识不会太深刻,但是把这些真实的感受也记录下来,觉得挺有意思的,可能有些认识是片面或者是不正确的,但可以通过后面更深入细致的分析过程,不断的纠正错误和深化理解。
源码分析是一个过程,经验是逐步累积起来的,希望文字可以把这种累积的感觉也准确记录下来。
现在就看看对nginx源码的第一印象吧。
源码包解压之后,根目录下有几个子目录和几个文件,最重要的子目录是auto和src,最重要的文件是configure脚本,不同于绝大多数的开源代码,nginx的configure脚本是作者手工编写的,没有使用autoconf之类的工具去自动生成,configure脚本会引用auto目录下面的脚本文件来干活。
根据不同的用途,auto目录下面的脚本各司其职,有检查编译器版本的,有检查操作系统版本的,有检查标准库版本的,有检查模块依赖情况的,有关于安装的,有关于初始化的,有关于多线程检查的等等。
configure作为一个总驱动,调用这些脚本去生成版本信息头文件、默认被包含的模块的声明代码和Makefile文件,版本信息头文件(ngx_auto_config.h,ngx_auto_headers.h)和默认被包含的模块的声明代码(ngx_modules.c)被放置在新创建的objs目录下。
要注意的是,这几个生成的文件和src下面的源代码一样重要,对于理解源码是不可忽略的重要部分。
src是源码存放的目录,configure创建的objs/src目录是用来存放生成的.o文件的,注意区分一下。
src按照功能特性划分为几个部分,对应着是几个不同的子目录。
src/core存放着主干部分、基础数据结构和基础设施的源码,main函数在src/core/nginx.c中,这是分析源码的一个很好的起点。
src/event存放着事件驱动模型和相关模块的源码。
src/http存放着httpserver和相关模块的源码。
src/mail存放着邮件代理和相关模块的源码。
src/misc存放着C++兼容性测试和googleperftools模块的源码。
src/os存放着依赖于操作系统实现的源码,nginx启动过程中最重要的master和workers创建代码就在这个目录下,多少让人觉得有点意外。
nginx的实现中有非常多的结构体,一般命名为ngx_XXX_t,这些结构体分散在许多头文件中,而在src/core/ngx_core.h中把几乎所有的头文件都集合起来,所有的实现文件都会包含这个ngx_core.h头文件,说nginx的各部分源码耦合厉害就是这个原因,但实际上nginx各个部分之间逻辑上是划分的很清晰的,整体上是一种松散的结构。
nginx实现了一些精巧的基础数据结构,例如ngx_string_t,ngx_list_t,ngx_array_t,ngx_pool_t,ngx_buf_t,ngx_queue_t,ngx_rbtree_t,ngx_radix_tree_t等等,还有一些重要的基础设施,比如log,configurefile,time等等,这些数据结构和基础设施频繁的被使用在许多地方,这会让人感觉nginx逻辑上的联系比较紧密,但熟悉了这些基础数据结构的实现代码就会感觉到这些数据结构都是清晰分明的,并没有真正的耦合在一起,只是有些多而已,不过nginx中“家酿”的代码也正是它的一个很明显的亮点。
nginx是高度模块化的,可以根据自己的需要定制模块,也可以自己根据一定的标准开发需要的模块,已经定制的模块会在objs/ngx_modules.c中声明,这个文件是由configure生成的。
nginx启动过程中,很重要的一步就是加载和初始化模块,这是在ngx_init_cycle中完成的,ngx_init_cycle会调用模块的hook接口(init_module)对模块初始化,ngx_init_cycle还会调用ngx_open_listening_sockets初始化socket,如果是多进程方式启动,就会调用ngx_master_process_cycle完成最后的启动动作,ngx_master_process_cycle调用ngx_start_worker_processes生成多个工作子进程,ngx_start_worker_processes调用ngx_worker_process_cycle创建工作内容,如果进程有多个子线程,这里也会初始化线程和创建线程工作内容,初始化完成之后,ngx_worker_process_cycle会进入处理循环,调用ngx_process_events_and_timers,该函数调用ngx_process_events监听事件,并把事件投递到事件队列ngx_posted_events中,最终会在ngx_event_thread_process_posted中处理事件。
事件机制是nginx中很关键的一个部分,linux下使用了epool,freebsd下使用了kqueue管理事件。
最后附上Joshua友情提供的源码大图一张,感谢:
)
可在
nginx源码分析(3)- 自动脚本
nginx的自动脚本指的是configure脚本程序和auto子目录下面的脚本程序。
自动脚本完成两件事情,其一是检查环境,其二是生成文件。
生成的文件有两类,一类是编译代码需要的Makefile文件,一类是根据环境检查结果生成的c代码。
生成的Makefile很干净,也很容易阅读。
生成的c代码有三个文件,ngx_auto_config.h是根据环境检查的结果声明的一些宏定义,这个头文件被include进ngx_core.h中,所以会被所有的源码引用到,这确保了源码是可移植的;ngx_auto_headers.h中也是一些宏定义,不过是关于系统头文件存在性的声明;ngx_modules.c是默认被包含进系统中的模块的声明,如果想去掉一些模块,只要修改这个文件即可。
configure是自动脚本的总驱动,它通过组合auto目录下不同功能的脚本程序完成环境检查和生成文件的任务。
环境检查主要是三个部分:
编译器版本及支持特性、操作系统版本及支持特性、第三方库支持,检查的脚本程序分别存放在auto/cc、auto/os、auto/lib三个子目录中。
检查的方法很有趣,通过自动编译用于检查某个特性的代码片段,根据编译器的输出情况判定是否支持该种特性。
根据检查的情况,如果环境足以支持运行一个简单版本的nginx,就会生成Makefile和c代码,这些文件会存放在新创建的objs目录下。
当然,也可能会失败,假如系统不支持pcre和ssh,如果没有屏蔽掉相关的模块,自动脚本就会失败。
auto目录下的脚本职能划分非常清晰,有检查环境的,有检查模块的,有提供帮助信息的(./configure--help),有处理脚本参数的,也有一些脚本纯粹是为了模块化自动脚本而设计出来的,比如feature脚本是用于检查单一特性的,其他的环境检查脚本都会调用这个脚本去检查某个特性。
还有一些脚本是用来输出信息到生成文件的,比如have、nohave、make、install等。
之所以要在源码分析中专门谈及自动脚本,是因为nginx的自动脚本不是用autoconf之类的工具生成的,而是作者手工编写的,并且包含一定的设计成分,对于需要编写自动脚本的人来说,有很高的参考价值。
这里也仅仅是粗略的介绍一下,需要详细了解最好是读一下这些脚本,这些脚本并没有使用多少生僻的语法,可读性是不错的。
btw,后面开始进入真正的源码分析阶段,nginx的源码中有非常多的结构体,这些结构体之间引用也很频繁,很难用文字表述清楚之间的关系,觉得用图表是最好的方式,因此需要掌握一种高效灵活的作图方法,我选择的是graphviz,这是at&t贡献的跨平台的图形生成工具,通过写一种称为“thedotlanguage”的脚本语言,然后用dot命令就可以直接生成指定格式的图,很方便。
nginx源码分析(4)-方法
(1)
看了几天的源码,进度很慢,过于关注代码实现的细节了,反而很难看清整体结构。
于是问诸google寻找方法。
大体上分析源代码都要经历三遍过程:
第一遍是浏览,通过阅读源码的文档和注释,阅读接口,先弄清楚每个模块是干什么的而不关心它是怎么做的,画出架构草图;
第二遍是精读,根据架构草图把系统分为小部分,每个部分从源码实现自底向上的阅读,更深入细致的理解每个模块的实现方式以及与模块外部的接口方式等,弄明白模块是怎么做的,为什么这样做,有没有更好的方式,自己会如何实现等等问题;
第三遍是总结回顾,完善架构图,把架构图中那些模糊的或者空着的模块重新补充完善,把一些可复用的实现放入自己的代码库中。
现在是浏览阶段,并不适合过早涉及代码的实现细节,要借助nginx的文档理解其整体架构和模块划分。
经过几年的发展,nginx现在的文档已经是很丰富了,nginx的英文wiki上包含了各个模块的详细文档,faq也涵盖了很丰富的论题,利用这些文档足以建立nginx的架构草图。
所以浏览阶段主要的工作就是阅读文档和画架构草图了。
对于源码分析,工具是相当关键的。
这几天阅读源码的过程,熟悉了三个杀手级的工具:
scrapbook离线文件管理小程序、graphviz图形生成工具、leo-editor文学编程理念的编辑器。
scrapbook是firefox下一款轻量高效的离线文件管理扩展程序,利用scrapbook把nginx的wiki站点镜像到本地只需要几分钟而已,管理也相当简单,和书签类似。
graphviz是通过编程画图的工具集合,用程序把图形的逻辑和表现表示出来,然后通过命令行执行适当的命令就可以解析生成图形。
leo-editor与其说是一个工具平台,不如说是一套理念。
和其他编辑器ide不同的是,leo关注的是文章内容的内在逻辑和段落层次,文章的表现形式和格式是次要的。
用leo的过程,其实就是在编程,虽然刚开始有些不适应,但习惯之后确实很爽,杀手级的体验感,很听话。
nginx源码分析(5)-方法
(2)
利用nginxwiki和互联网收集了不少nginx相关的文档资料,但是仔细阅读之后发觉对理解nginx架构有直接帮助的资料不多,一些有帮助的资料也要结合阅读部分源码细节才能搞清楚所述其是,可能nginx在非俄国之外的环境下流行不久,应用还很简单,相关的英文和中文文档也就不够丰富的原因吧。
不过还是有一些金子的。
如果要了解nginx的概况和使用方法,wiki足以满足需要,wiki上有各个模块的概要和详细指令说明,也有丰富的配置文件示例,不过对于了解nginx系统架构和开发没有相关的文档资料。
nginx的开发主要是指撰写自定义模块代码。
这需要了解nginx的模块化设计思想,模块化也是nginx的一个重要思想,如果要整体上了解nginx,从模块化入手是一个不错的起点。
emiller的nginx模块开发指引是目前最好的相关资料了(http:
//emiller.info/nginx-modules-guide.html),这份文档作为nginx的入门文档也是合适的,不过其中有些内容很晦涩,很难理解,要结合阅读源码,反复比对才能真正理解其内涵。
如果要从整体上了解nginx架构和源码结构,Joshuazhu的广州技术沙龙讲座的pdf和那张大图是不错的材料,这份pdf可以在wiki的资源页面中找到地址链接。
Joshua也给了我一些建议和指引,使我少走了不少弯路,很快进入状态,感谢。
相信最好的文档就是源码本身了。
随着阅读源码的量越来越大,也越来越深入,使我认识到最宝贵的文档就在源码本身,之前提到过,nginx的代码质量很高,命名比较讲究,虽然很少注释,但是很有条理的结构体命名和变量命名使得阅读源码就像是阅读文档。
不过要想顺利的保持这种感觉也不是一件简单的事情,觉得要做好如下几点:
1)熟悉C语言,尤其是对函数指针和宏定义要有足够深入的理解,nginx是模块化的,它的模块化不同于apache,它不是动态加载模块,而是把需要的模块都编译到系统中,这些模块可以充分利用系统核心提供的诸多高效的组件,把数据拷贝降到最低的水平,所以这些模块的实现利用了大量的函数指针实现回掉操作,几乎是无函数指针不nginx的地步。
2)重点关注nginx的命名,包括函数命名,结构体命名和变量命名,这些命名把nginx看似耦合紧密的实现代码清晰的分开为不同层次不同部分的组件和模块,这等效于注释。
尤其要关注变量的命名,后面关于模块的分析中会再次重申这一点。
3)写一个自定义的模块,利用nginx强大的内部组件,这是深入理解nginx的一个有效手段。
接下来的分析过程,着眼于两个重点,一个就是上面提到的模块化思想的剖析,力争结合自身理解把这个部分分析透彻;另一个重点是nginx的事件处理流程,这是高性能的核心,是nginx的core。
nginx源码分析(6)-模块化
(1)
源码的src/core目录下实现了不少精巧的数据结构,最重要的有:
内存池ngx_pool_t、缓冲区ngx_buf_t、缓冲区链ngx_chain_t、字符串ngx_str_t、数组ngx_array_t、链表ngx_list_t、队列ngx_queue_t、基于hash的关联数组ngx_hash_t、红黑树ngx_rbtree_t、radix树ngx_radix_tree_t等,这些数据结构频繁出现在源码中,而且这些结构之间也是彼此配合,联系紧密,很难孤立某一个出来。
每个数据结构都提供了一批相关的操作接口,一般设置数据的接口(add)只是从内存池中分配空间并返回指向结构体的指针,然后利用这个指针再去设置成员,这使得此类接口的实现保持干净简洁。
这些数据结构的实现分析是有一定难度的,不过我觉得不了解实现也并不影响理解整体的架构,只要知道有这么些重要的数据结构和基本的功用就足以满足需要了,我会在搞清楚整体架构之后再详细分析一下这些精致的玩意。
nginx的模块化架构和实现方式是源码分析系列中最关键的一个部分。
模块化是nginx的骨架,事件处理是nginx的心脏,而每个具体模块构成了nginx的血肉,摸清骨架,才能游刃有余。
先高屋建瓴的快速浏览一下nginx的模块化架构,后面再详细分析每一个重要数据结构和实现方式。
nginx的模块在源码中对应着是ngx_module_t结构的变量,有一个全局的ngx_module_t指针数组,这个指针数组包含了当前编译版本支持的所有模块,这个指针数组的定义是在自动脚本生成的objs/ngx_modules.c文件中,下面是在我的机器上编译的nginx版本(0.8.9)的模块声明:
externngx_module_t ngx_core_module;
externngx_module_t ngx_errlog_module;
externngx_module_t ngx_conf_module;
externngx_module_t ngx_events_module;
externngx_module_t ngx_event_core_module;
externngx_module_t ngx_epoll_module;
externngx_module_t ngx_http_module;
externngx_module_t ngx_http_core_module;
externngx_module_t ngx_http_log_module;
externngx_module_t ngx_http_upstream_module;
externngx_module_t ngx_http_static_module;
externngx_module_t ngx_http_autoindex_module;
externngx_module_t ngx_http_index_module;
externngx_module_t ngx_http_auth_basic_module;
externngx_module_t ngx_http_access_module;
externngx_module_t ngx_http_limit_zone_module;
externngx_module_t ngx_http_limit_req_module;
externngx_module_t ngx_http_geo_module;
externngx_module_t ngx_http_map_module;
externngx_module_t ngx_http_referer_module;
externngx_module_t ngx_http_rewrite_module;
externngx_module_t ngx_http_proxy_module;
externngx_module_t ngx_http_fastcgi_module;
externngx_module_t ngx_http_memcached_module;
externngx_module_t ngx_http_empty_gif_module;
externngx_module_t ngx_http_browser_module;
externngx_module_t ngx_http_upstream_ip_hash_module;
externngx_module_t ngx_http_write_filter_module;
externngx_module_t ngx_http_header_filter_module;
externngx_module_t ngx_http_chunked_filter_module;
externngx_module_t ngx_http_range_header_filter_module;
externngx_module_t ngx_http_gzip_filter_module;
externngx_module_t ngx_http_postpone_filter_module;
externngx_module_t ngx_http_charset_filter_module;
externngx_module_t ngx_http_ssi_filter_module;
externngx_module_t ngx_http_userid_filter_module;
externngx_module_t ngx_http_headers_filter_module;
externngx_module_t ngx_http_copy_filter_module;
externngx_module_