深入剖析Nginx

978-7-115-30762-0
作者: 高群凯
译者:
编辑: 陈冀康

图书目录:

详情

本书重点在于通过剖析Nginx的源代码,探究其功能结构及其内部实现原理。全书共包括14章和3个附录。首先介绍了开始剖析Nginx源代码前的准备工作,以及跟踪和调试的方法;然后,分别深入分析了Nginx的进程模型、数据结构、配置指令、主要功能模块、I/O事件处理、变量机制、客户端请求过程、Filter模块实例、负载均衡策略以及Handler模块等。

图书摘要

深入剖析Nginx
高群凯 著
人民邮电出版社

北京

前言

慕名对Nginx源码进行学习与研究是早在2008年的事情。当时正在为职业规划与未来发展困惑不已,一筹莫展之际不知从哪里得知高性能服务器是一个很有“前途”的努力方向,几经搜索又机缘偶合地得识Lighttpd与Nginx。在逐步了解和熟悉它们的源码后,我开始感到自己的无知与浅薄,发现原来代码也可以写得如此优雅。

我已编著过一本《Lighttpd源码分析》。先解析Lighttpd源码并没有什么特别的原因,只是因为在当时Lighttpd比Nginx要火,应用得也较广;而近几年,凭借专注持续的更新与运作,Nginx后来居上,在全世界的应用仅次于Apache与MicrosoftIIS[1],而且大有赶超IIS的势头。

Nginx 提供了一个安全、快速并且灵活的Web Server 环境。与其他Web Servers 相比,其主要特点是占有系统资源少、并发能力强、稳定性好,这些都是吸引网站架构人员重点关注以及对其进行广泛应用的巨大优点。然而,对于开发工程师而言,更有吸引力的地方却是Nginx 是怎样做到如此强劲的功能与性能的,这从因特网上存在大量的 Nginx 源码分析的博客文章与论坛帖子可见一斑。

既然互联网上已有大量的 Nginx 源码分析文章,那我现在编写这本《Nginx 源码分析》是否多此一举?我会说不是,毕竟它较为完整地梳理了 Nginx 的相关核心流程。当然,这只是我给出的答案,最终还需要各位读者来回答。我在此阐述一下自己的看法。本书的原始素材是我在最近三四年里地对 Nginx 代码分析时断断续续地所做的笔记,只不过之前写的笔记过细地逐行注释代码,导致文档中充斥大量的代码。而在这次重新整理后,希望能从比较高一点的角度去解析 Nginx,把握全局,搞清楚整体实现原理而不是陷入细节。我个人认为,只要看清楚了 Nginx 整体的实现原理,对于一时半会没有触及到的细节,在真正遇到那个点时再去理解也是毫不费力,最多只不过还需去查一下Man手册,了解一下系统API而已。授人以鱼不如授人以渔,为了达到这个目标,本书力争尽量少贴代码多画图,当然一些必要的代码仍然是不可或缺的,所以读者还是会在本书看到源代码。虽然我的最初出发点是好的,但是在解析 Nginx 的某些功能时,我的确遇到了困难,特别是在解析到一大片代码却并没有孰轻孰重的情况下,实在让我很纠结,把代码全部贴上将占去大量的版面,会违背我少贴代码的初衷,不贴上又怕读者不知我所云何物而看不懂。这不仅是因为 Nginx 代码本身的耦合性较大,也在于我个人的文字描述能力有待提高,所以导致本书或多或少的各种缺点,比如啰嗦的地方、没讲到的细节等,还请各位读者多多包涵。

读者对象

我面试过不少刚本科毕业或研究生毕业的应届生,问得比较多的一句是:写过实际应用的代码么?答:没有。我又问:看过什么优秀的开源代码么?答:没有。这种情况很糟糕。限于国内大学的环境,除了较为简单的课程设计以外,很多大学生在校园内很少有机会写代码或参与实践问题的解决。针对这一点,至少我们可以去看一些有价值的优秀开源代码,毕竟在大学校园里,我们有时间、有地点、有资源(教室、图书馆、电子阅览室等)。所谓他山之石,可以攻玉,在理解这些开源代码的过程中,我们甚至可以尝试写一些扩展功能模块,逼迫自己去主动学习,培养扎实的理论基础知识,锻炼实际的动手编程能力,自己也会因此逐步成长许多。

本书的另一目标读者群为软件开发工程师,这是显而易见的事情。短小精悍的 Nginx 实现了如此强大的 HTTP 服务、反向代理服务以及邮件代理服务等,这些功能值得每一位软件开发工程师去学习和研究,况且国内也有不少项目在对 Nginx 进行二次开发或移植整合,有一本能帮助开发工程师快速开展工作的书籍也许能加快项目进度。

另外,鉴于国内对Nginx的广泛使用,虽然Nginx官网上有不少针对配置使用的文档,但是限于文字描述的简洁性与文档更新的滞后性,对于运维工程师而言,一些配置指令的使用是靠逐步验证来判断其具体使用用法的。通过本书的介绍,我们能从源代码上去找对应配置指令的用法并理解其真实的实际功能,即它让源代码本身就变成一本配置使用手册。所以,对于那些具有强烈意愿知其然又知其所以然的高级运维工程师,本书也是一个不错的选择。

我假设本书的读者已经掌握了C语言,并且对Linux系统有一定程度的了解。不过,即使在开始阅读本书之前,读者在这些方面存在一些不足也无关紧要,我会尽可能地把相关扩展链接标注出来,读者查询一下相关资料即可。

本书的读者对象主要是如下三类人群:

在校大学生;

软件开发工程师;

Nginx 高级运维工程师。

Nginx版本

本书基于 Nginx-1.2.0,该版本是我在重新开始做文档整理时 Nginx 的最新版本。Nginx源码更新较为频繁,但更新的主要是Bug修正或增添新功能,而其主要架构是稳定的,所以不论哪个版本的Nginx,本书基本都是适用的。

本书讨论环境

我是一名Linux开发工程师,所以本书的讨论环境也就是以Linux为主,使用的所有相关工具都是Linux[2]下的,比如调试工具gdb、编译工具gcc、测试命令strace/pstack、测试工具wget[3]、curl[4]等。

Nginx 本身提供的相关机制,对各种平台都进行了支持,比如 I/O 多路复用模型就支持epoll、kqueue、eventport等,但本书仍然以Linux平台上的机制为主要讨论对象,像I/O多路复用模型里就是epoll。在没有明确说明的情况,默认的编译模块以附录A为准,且默认以附录B给出的配置运行Nginx。

Linux是广泛使用的操作系统,所以以它为主要实例也是最为方便且有效的方法。我在Intel x86 机器上安装了一个CentOS[5]6.2 的32 位操作系统(系统设置全为默认,比如页大小4KB,应用程序与内核的地址空间划分为3:1 等),并且把CentOS 6.2 所提供的相关软件开发包也都选择安装上了,这对于我在后继执行相关程序的编译与安装时提供了极大的方便,下面列出了相关系统环境:

操作系统:CentOS release 6.2 (Final)/kernel-2.6.32/32bit;

编译器:gcc[6]version4.4.620110731 (Red Hat 4.4.6-3) (GCC);

调试器:GNU gdb[7](GDB) Red Hat Enterprise Linux (7.2-50.el6);

Make:GNU Make[8]3.81。

本书内容

本书不是关于Nginx配置指令如何使用的介绍手册,关于那些内容在Nginx官网上有专门的帮助文档[9],甚至有对应的中文翻译[10]。因此本书内容的重点在于解析Nginx的内部实现原理。

虽然限于时间关系而无法做到方方面面的解析,但我尽力把Nginx最核心的线条抽取出来并努力把它们以一种更容易理解的形式展现在各位读者面前。本书一共14章,大部分章节在排列上并没有特别的先后顺序,所以读者在翻阅本书时可以来回跳跃。各章内容简介如下:

第1章,介绍开始本书内容前的准备工作。这是一些基础概念和工具使用,比如什么是Nginx、怎样安装运行Nginx、如何快速方便地阅读Nginx源码以及有哪些相关的知识站点等。

第2章,详细介绍了跟踪与调试的多种手段与技巧。通过实际案例可以看到,这些知识能够极为方便地帮助我们理解Nginx程序内部的相关执行逻辑。

第3章,从3个层次上介绍Nginx的进程模型。首先是最顶层,通过Nginx整体架构框图从宏观上了解Nginx;其次是中间层,对监控进程、工作进程、Cache进程进行逐一介绍,了解它们各自的主要执行逻辑;最后是交互层,即进程之间(套接字、共享内存)以及进程与用户之间(信号)的信息交换。

第4章,Nginx封装了很多有用的数据结构,大多较为简单一看即懂,但其中的内存池、Hash 和Radix tree 这3 个数据结构较为复杂,所以本章对它们做了详细介绍。

第5章,配置指令是用户控制Nginx实际运行逻辑的主要手段,如何将用户设置的配置值转换到Nginx内部并控制Nginx的具体执行,是本章的解析重点。

第6章,Nginx 提供的丰富功能基本都是通过模块来实现的,根据每个模块的具体功能不同而分为不同的类型,比如Handler模块、Filter模块、Load-balance模块等。通过对它们的综合概述,让读者从宏观上了解每类Nginx模块的功能。

第7章,与事件相关的实现将在本章介绍。这包括I/O多路复用模型、I/O读写事件、超时事件等。另外,由于 Nginx 工作进程的主要任务就是处理事件,所以各个工作进程之间的负载均衡也一并介绍。

第8章,变量机制。这里所提到的变量主要是指Nginx用户在配置文件里所使用的可变符号,这些符号大多会随着客户端请求的不同而不同,类似于编程语言里的变量,本章将详细阐述Nginx对它们的具体实现。

第9章,介绍一个完整的客户端请求,包括Web服务器端处理该请求并响应相关数据的过程。在这个过程中,Nginx 充当 Web 服务器直接处理客户端请求,而不是转发到后端服务器。

第10章,对于到达Nginx的客户端请求,Nginx首先要做的就是对它进行定位,也就是找到其所对应的 Server 与 Location,从而提供处理该请求的正确上下文环境,请求处理才能得以继续。这就是本章的主要内容。

第11章,与第9章内容类似,但本章提到的Nginx充当的是代理角色,即它把请求转发给后端PHP服务器进行处理,然后接收其响应并把响应数据发给最终客户端,即一个完整的Nginx+Fastcgi+PHP的客户端请求处理响应过程。

第12章,详细介绍一些 Filter 模块实例,比如 ngx_http_not_modified_filter_module、ngx_http_headers_filter_module等。

第13章,详细介绍两种Load-balance策略的实现,即加权轮询策略与IP哈希策略。

第14章,详细介绍Nginx如何通过Handler模块实现对客户端的访问控制。

另外,附录A给出的是默认configure下的Nginx编译模块,附录B是编写本书时所默认使用的Nginx运行配置,而附录C给出一个HTTP状态码简单介绍的列表,方便读者查阅。

最后,就我个人而言,本书最大的遗憾就是其目前的层次仅达到What与How的程度,即它只介绍了 Nginx 是什么,有什么功能特性,内部如何运作,具体如何实现,而没有介绍其为什么是这样而不是那样,为什么这样的设计能达到高性能。限于时间关系,这第三个层次(即Why)的内容就暂留给各位读者自己去独立思考吧。不过在此之前,需要充分理解和掌握本书所介绍的前两个层次内容。

致谢

首先,感谢Igor Sysoev[11]大师以及Nginx Inc.[12]为我们提供了如此优秀的开源项目,他们对Nginx的持续改进,让我们看到越来越强大的Nginx。

感谢互联网上众多的知识分享者,帮助我对 Nginx 代码的理解,特别是在我开始重新整理Nginx文档时,互联网上已涌现出大量的Nginx相关文档,虽然比较零散,但的确加速了我对文档的整理进度。这些内容大多来自博客、论坛、问答等,虽然我无法把他们一一列出,但这并不妨碍我对他们的感激之情。部分参考链接如下。

http://openresty.org/

http://tengine.taobao.org/book/index.html

http://blog.csdn.net/dingyujie/article/category/782920

http://www.aosabook.org/en/nginx.html

http://www.evanmiller.org/nginx-modules-guide.html

http://www.evanmiller.org/nginx-modules-guide-advanced.html

http://blog.lifeibo.com/?cat=4

http://www.pagefault.info/?cat=7

感谢本书编辑对我的各种指导,使我的写作水平有了很大的提高。

最后,感谢在工作和生活中帮助过我的所有人,感谢你们,正是因为有了你们,才有了本书的面世。

本书的相关网站

我维护了一个包含本书相关信息的网站:http://lenky.info/ngx_book/,其中主要包括了本书的勘误表、内容扩展和修改;同时,如果读者有关于本书或Nginx的任何问题,都可以在此页面进行留言,我会尽量经常查看并且及时回复读者提出的相关问题。另外,我的个人电子邮箱为:lenky0401@gmail.com。

作为本书的作者,我以兢兢业业的态度力求做到准确无误,但限于时间仓促和个人水平,书中难免会有一些纰漏,这不乏笔误,有叙述不清楚的地方,甚至有我个人理解的错误。针对这些可能存在的不足给读者带来的任何困惑与阻碍,在此提前说声抱歉,请各位读者和同行多多包涵,如果能提出宝贵的意见,我将洗耳恭听,感激不尽。最后,也请大家时时关注一下上面提到的这个网址,有任何问题,我都会及时在那公布。

注 释

[1].http://news.netcraft.com/archives/2012/08/02/august-2012-web-server-survey.html。

[2].虽然有一些工具也的确能使用于Windows 等其他平台下。

[3].http://www.gnu.org/software/wget/

[4].http://curl.haxx.se/

[5].http://www.centos.org/

[6].http://gcc.gnu.org/

[7].http://www.gnu.org/software/gdb/

[8].http://www.gnu.org/software/make/

[9].http://www.nginx.org/en/docs/和http://wiki.nginx.org/DirectiveIndex

[10].http://wiki.nginx.org/NginxChs

[11].http://sysoev.ru/en/

[12].http://www.nginx.com/

作者简介

高群凯,重庆大学计算机硕士毕业,在技术领域的兴趣主要包括Linux、Lighttpd、Nginx、Mongodb,以及X86、MIPS(Cavium、RMI、Tilera)等硬件架构。毕业之后,一直在深信服科技有限公司[1]工作,专职从事Linux相关研究,对系统底层性能优化、内核宕机疑难排查等相关技术领域具有一定的经验。曾著有《Lighttpd源码分析》一书。

注 释

[1].http://www.sangfor.com/

第1章 源码分析的准备工作

从Nginx(读作engine x)的官方网站[1],我们可以看到如下介绍:Nginx是Igor Sysoev[2]编写的一款HTTP和反向代理服务器,另外它也可以当作邮件代理服务器。它一直被众多流量巨大的俄罗斯网站所使用,例如Yandex[3]、Mail.Ru[4]、VKontakte[5]以及Rambler[6]等。据Netcraft统计,截止到2012年8月份,世界上最繁忙的网站中有11.48%[7]在使用Nginx作为其服务器或者代理服务器。部分典型成功案例有:Netflix[8]、Wordpress.com[9]和FastMail.FM[10]。鉴于Nginx的强大性能与稳定性,在国内也有大量的高压力网站在使用Nginx,如新浪、网易、腾讯、CSDN、酷六、水木社区、豆瓣等。

1.1 主要特性

作为轻量级HTTP服务的典型代表,Nginx除了具备体积小、配置灵活、并发能力强、稳定等众所周知的特点以外,在官方网站还详细列出了Nginx的一些主要特性,我们来详细了解一下[11]

1.HTTP服务基本特性

• 处理静态页面请求;

• 处理index 首页请求;

• 对请求目录进行列表显示;

• 支持多进程间的负载均衡;

• 对打开文件描述符进行缓存(提高性能);

• 对反向代理进行缓存(加速);

• 支持FastCGI、uwsgi、SCGI 和memcached 多种后端服务器;

• 支持gzip、ranges、chunked、XSLT、SSI 以及图像缩放;

• 支持SSL、TLS SNI。

2.HTTP服务高级特性

• 基于名称的虚拟主机;

• 基于IP 的虚拟主机;

• 支持Keep-alive 和pipelined 连接;

• 灵活和方便的配置;

• 在更新配置和升级执行程序时提供不间断服务;

• 可自定义客户端访问的日志格式;

• 带缓存的日志写操作(提高性能);

• 支持快速的日志文件切换;

• 支持对3xx-5xx 错误代码进行重定向;

• URI 重写支持正则表达式;

• 根据客户端地址执行不同的功能;

• 支持基于客户端IP 地址的访问控制;

• 支持基于HTTP 基本认证机制的访问控制;

• 支持HTTPreferer 验证;

• 支持HTTP 协议的PUT、DELETE、MKCOL、COPY 以及MOVE 方法;

• 支持FLV流和MP4 流;

• 支持限速机制;

• 支持单客户端的并发控制;

• 支持Perl脚本嵌入。

3.邮件代理服务特性

• 使用外部HTTP 认证服务器将用户重定向到IMAP/POP3 服务器;

• 使用外部HTTP 认证服务器将用户重定向到内部SMTP 服务器;

• 支持的认证方式。

♦ POP3:USER/PASS、APOP、AUTH LOGIN/PLAIN/CRAM-MD5。

♦ IMAP:sLOGIN、AUTH LOGIN/PLAIN/CRAM-MD5。

♦ SMTP:AUTH LOGIN/PLAIN/CRAM-MD5。

• 支持SSL;

• 支持STARTTLS 和STLS。

4.架构和扩展性

• 一个主进程和多个工作进程配合服务的工作模型;

• 工作进程以非特权用户运行(安全性考虑);

• 支持的事件机制有:kqueue(FreeBSD 4.1+)、epoll(Linux 2.6+)、rt signals(Linux2.2.19+)、/dev/poll(Solaris 711/99+)、eventports(Solaris10)、select 和poll;

• 支持 kqueue 的众多特性,包括 EV_CLEAR、EV_DISABLE(临时禁止事件)、NOTE_LOWAT、EV_EOF等;

• 支持sendfile(FreeBSD3.1+、Linux2.2+、Mac OSX10.5+)、sendfile64(Linux2.4.21+)和sendfilev(Solaris8 7/01+);

• 支持异步文件IO(FreeBSD 4.3+、Linux2.6.22+);

• 支持DIRECTIO(FreeBSD 4.4+、Linux2.4+、Solaris2.6+、Mac OS X);

• 支持Accept-filters(FreeBSD4.1+、NetBSD5.0+)和TCP_DEFER_ACCEPT(Linux2.4+);

• 10000 个非活跃HTTPkeep-alive 连接仅占用约2.5MB 内存;

• 最少程度的数据拷贝操作。

5.已测试过的操作系统和平台

• FreeBSD 3~10/i386、FreeBSD5~10/amd64;

• Linux 2.2~3/i386、Linux2.6~3/amd64;

• Solaris9/i386、sun4u、Solaris10/i386、amd64、sun4v;

• AIX7.1/powerpc;

• HP-UX11.31/ia64;

• Mac OS X/ppc、i386;

• WindowsXP、Windows Server 2003。

从上面列表可以看到Nginx功能的丰富与强悍。当然,这里给出的还只是Nginx功能的简单描述,而对于每项功能的具体使用以及是如何实现的,我们还不得而知,而这也正是本书将要展开叙述的全部内容。

1.2 源码下载

Nginx的源码可通过官网提供的下载地址[12]找到,截止当前的最新版本是Nginx1.2.0,也就是本书所针对的版本。虽然官网下载页没有提供Nginx旧版源码的下载链接,但Nginx的所有版本源码包都是放在目录http://nginx.org/download/下的,所以包括Nginx 0.1.0 版本在内的Nginx源码都能下载到。

由于 Nginx 背后有公司运作,所以其更新速度比较快,相关资料也比较齐全,下面是一些有用的网址。

• 官方主页:http://nginx.org/。

• 使用手册:http://nginx.org/en/docs/。

• 配置指令:http://wiki.nginx.org/DirectiveIndex。

• 版本任务:http://trac.nginx.org/nginx/report/2。

• 开发路线图:http://trac.nginx.org/nginx/roadmap。

• 邮件讨论组:http://mailman.nginx.org/mailman/listinfo。

所有相关信息基本都能从官方主页链入,如果想找什么资料,建议先去官网看看。

1.3 源码目录结构

将Nginx源码包解压后,目录文件如下所示。

[root@localhost nginx-1.2.0]# ls -F

auto/ CHANGES CHANGES.ru conf/ configure* contrib/ html/ LICENSE man/ README src/

其中

• auto/:包含了很多会在执行configure 进行编译配置时调用的检测代码。

• CHANGES:Nginx 的版本更新细节记录。英文版。

• CHANGES.ru:Nginx 的版本更新细节记录。俄文版。

• conf/:Nginx 提供的一些默认配置文件。

• configure*:根据系统环境设定Nginx 编译选项的执行脚本。

• contrib/:网友贡献的一些有用脚本。

• html/:提供了两个默认html 页面,比如index.html 的Welcome tonginx!。

• LICENSE:声明的Nginx 源码许可协议。

• man/:Nginx 的Man手册,本文文件,可直接用vi 或记事本打开。

• README:读我文件,内容很简单,通告一下官网地址。

• src/:Nginx 源码,分门别类,比如实现事件的event 等,很清晰。

执行configure脚本后将生成Makefile文件和objs目录,这是根据当前系统环境生成的相关编译配置。Nginx并没有使用Autoconf[13]和Automake[14]等这样的自动化工具来做这个工作,而都是手动编码实现的。比如当Nginx判断当前Linux系统是否支持epoll时,它采用的方法就是编写一款小应用程序,并在其中调用epoll_create()函数,然后再根据它是否可被正常编译执行来做这个判断。具体可参考文件nginx-1.2.0/auto/os/linux和nginx-1.2.0/auto/feature内相关代码。

1.4 源码分析工具

对于Windows平台,首选Source Insight[15]源码阅读工具。该工具功能强大,根据其官方网站的介绍,Source Insight是一款面向项目开发的程序编辑器和代码浏览器,它拥有内置的对C/C++、C#和Java等程序的分析功能。SourceInsight能自动分析和动态维护源码工程的符号数据库,并在用户查看代码时显示有用的对应上下文信息。

如果是在Linux平台下,则可以利用Vi[16]、Taglist[17]、Cscope[18]以及Ctag[19]这几个工具来组合成阅读Nginx源码的环境。它们的组合也许要费一段功夫,但磨刀不误砍柴工,为了更方便快捷地阅读Nginx源码,花这点时间还是比较值得的。

当然,我们还有另外一个更方便简单的选择:Source Navigator[20]。Source Navigator(Sourcenav)是由Red Hat推出的一款查看和分析源代码的强大图形界面工具,可以与前面介绍的Source Insight相媲美,而且Sourcenav是开源的。除了提供源代码的编辑、查看功能, Sourcenav同时还支持编译器和调试器的集成,因此可以构建成一套完整的IDE开发环境。Sourcenav针对Windows和UNIX/Linux,提供两种版本,在Windows下的版本,解压即可以使用,但是要注意解压路径不能包含空格以及中文字符。图1-1所示是Sourcenav在Ubuntu 8.10平台下的运行界面。

不管是在Windows平台下还是在Linux平台下,搭建一个得心应手的源码阅读环境,是我们阅读源码达到事半功倍效果的有力保证。

图1-1 Sourcenav在Ubuntu 8.10平台下的运行界面

1.5 测试辅助工具

我们将在第2章里介绍如何对Nginx进行跟踪与调试,除了对Nginx进程进行直接的跟踪与调试的工具以外,还会用到另外两个HTTP测试工具:wget[21]与curl[22]。关于这两个工具的差别,可以看这里[23],要注意的主要是wget 1.12 及以前的版本仅支持HTTP 1.0 协议(虽然也包括部分HTTP1.1 的特性),所以在测试HTTP1.1的相关特性时,最好使用wget-1.13 以后版本或curl。另外,通过给wget加上--debug选项或给curl加上-v选项能看到它们请求的详细信息[24],这对我们的测试提供的帮助也非常大。

我用到的其他测试辅助工具主要还有如下几个。

• Wireshark[25]:抓包使用。

• Nc[26]:网络工具中的瑞士军刀,短小精悍,功能强大[27]。

• Firefox[28]:结合firebug[29]看HTTP请求响应内容。

• Opera[30]:浏览器,测试HTTP。

• Hexdump[31]:看十六进制数据。

1.6 编译与执行

Nginx的编译安装很简单,使用Linux下通用的三板斧即可:./configure、make、make install。当然,这样做的话,那么一切都是使用的默认配置,如果要做修改,则必须在执行 configure时指定,比如对Nginx加上调试功能。

[root@localhost nginx-1.2.0]# ./configure --with-debug

修改默认安装路径。

[root@localhost nginx-1.2.0]# ./configure --prefix=/usr/gqk/

所有这些配置选项可以通过命令查看。

[root@localhost nginx-1.2.0]# ./configure –help

在默认情况下,Nginx被安装在/usr/local/nginx/目录下,而其他目录也大都以此为父目录,比如 Web 根目录为/usr/local/nginx/html/,日志记录在文件/usr/local/nginx/logs/access.log 和/usr/local/nginx/logs/error.log内。

编译好后的Nginx,执行它很简单,一般我们只需指定配置文件即可。

[root@localhost~]#/home/gqk/nginx-1.2.0/objs/nginx-c/usr/local/nginx/conf/nginx.conf.test

如果不指定配置文件,那么默认就是安装目录下的 nginx.conf 文件,比如:/usr/local/nginx/conf/nginx.conf。通过ps命令可以看到Nginx是否已正常执行。

[root@localhost ~]# ps auxf | grep nginx | grep -v grep

root 3949 0.0 0.1 5216 572 ? Ss Oct05 0:00 nginx: master process /home/gqk/nginx-1.2.0/objs/nginx -c /usr/local/nginx/conf/nginx.conf.test

nobody 3950 0.0 0.3 5404 1236 ? T Oct05 0:00 \_ nginx: worker process

查看Nginx对应的监听套接口。

[root@localhost ~]# netstat -natp | grep nginx

tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN 3949/nginx

1.7 其他准备

本书主要针对的是Nginx的Web服务器功能,这其中牵扯到很多的国际标准协议,比如说HTTP协议、URL标准、HTML标准等。因此,把与之相关的RFC文档准备好是必不可少的。这里列出几个站点,方便查阅。

http://www.rfc.net RFC的官方站点[32],可以检查RFC最及时的更新情况。

http://www.ietf.org 最重要的 Internet组织之一。

http://sunsite.dk  RFC查询非常强大(可以以FTP登录下载全部RFC文档)。

http://www.iso.ch  ISO-国际标准化组织。

http://standards.ieee.org IEEE-电气与电子工程师协会。

http://web.ansi.org ANSI-美国国家标准化组织。

http://www.itu.int  ITU-国际电信同盟。

http://www.rfc-editor.org/ RFC归档搜索网。

http://www.faqs.org/rfcs/ RFC归档搜索网。

http://www.cnpaf.net/ 中国协议分析网。

注 释

[1].http://www.nginx.org/en/

[2].http://sysoev.ru/en/

[3].http://www.yandex.ru/

[4].http://www.mail.ru/

[5].http://vkontakte.ru/

[6].http://www.rambler.ru/

[7].http://news.netcraft.com/archives/2012/08/02/august-2012-web-server-survey.html

[8].https://signup.netflix.com/openconnect/software

[9].http://www.nginx.com/cs/nginx-automattic.html

[10].http://blog.fastmail.fm/2007/01/04/webimappop-frontend-proxies-changed-to-nginx/

[11].http://www.nginx.org/en/

[12].http://nginx.org/en/download.html

[13].http://www.gnu.org/software/autoconf/

[14].http://www.gnu.org/software/automake/

[15].http://www.sourceinsight.com/

[16].http://vim.sourceforge.net/

[17].http://vim-taglist.sourceforge.net/

[18].http://cscope.sourceforge.net/

[19].http://ctags.sourceforge.net/

[20].http://sourcenav.sourceforge.net/

[21].http://www.gnu.org/software/wget/

[22].2 http://curl.haxx.se/

[23].http://daniel.haxx.se/docs/curl-vs-wget.html

[24].http://lenky.info/?p=1841

[25].http://www.wireshark.org/

[26].http://netcat.sourceforge.net/

[27].http://linux.die.net/max/I/nc

[28].http://www.mozilla.org/en-US/firefox/fx/#desktop

[29].https://getfirebug.com/

[30].http://www.opera.com/

[31].ftp://ftp.kernel.org/pub/linux/utils/util-linux/

[32].要查阅rfc 文档直接访问http://www.ietf.org/rfc/rfc----.txt或http://tools.ietf.org/html/rfc----,将横线换成rfc 文档的对应序号,比如:http://www.ietf.org/rfc/rfc2616.txt或http://tools.ietf.org/html/rfc2616。

第2章 跟踪与调试

跟踪与调试,不仅是我们解决程序Bug的有力途径,也是帮助我们理解现有代码的有效方法。通过跟踪程序执行的过程,我们可以清楚地了解程序的内部逻辑,对于不明就里的实现细节,调试查看程序内部变量也能更好地帮助我们做出正确的理解。本章将介绍一些跟踪与调试程序的方法,除了最基本的gdb调试,我还将结合个人经验,介绍一些相对高级的应用技巧。

2.1 利用gdb 调试

gdb是Linux下调试程序的常用工具,任何Linux开发工程师初学程序调试时第一个接触到的工具应该就是gdb。关于gdb本身的详细用法,我们不多详述,读者可以参考gdb官网手册[1],而在这里,我们将重点介绍一些与Nginx相关的注意点与调试技巧。

2.1.1 绑定Nginx到gdb

利用gdb调式Nginx,首先得在生成Nginx程序时把-g编译选项打开。当然,这并不是说不打开-g选项就无法用gdb调试它,只是会因为缺少相应的符号信息导致调试不便,而此时可能也将获得“No symbol table is loaded.Use the "file" command.”的提示。上一章已经介绍了如何编译Nginx,在执行./configure 命令生成对应的objs/Makefile文件后,检查该文件里的CFLAGS变量是否已带上了-g选项[2],没有则加上即可。另一个值得关注的编译选项是-O0,如果在gdb内打印变量时提示“<value optimized out>”或gdb显示的当前正执行的代码行与源码匹配不上而让人感觉莫名其妙,那么,这多半是因为gcc的优化导致,我们可以加上-O0 选项来强制禁用gcc的编译优化。除了可以通过编辑objs/Makefile文件,把这两个选项直接加在CFLAGS变量里以外,还有另外几种方法也可以达到同样的效果。

1.在进行configure 配置时,按如下方式执行。

[root@localhost nginx-1.2.0]# ./configure--with-cc-opt='-g –00'

上面是利用configure所提出的选项[3]来做的,属于比较推荐的方法,但也可使用如下方法。

[root@localhost nginx-1.2.0]# CFLAGS="-g -O0" ./configure

2.在执行make时,按如下方式执行。

[root@localhost nginx-1.2.0]# make CFLAGS="-g -O0"

直接修改objs/Makefile文件和上面提到的第2种方法是在我们已经执行configure之后进行的,如果之前已经执行过 make,那么在进行第二次 make 时,需带上强制重新编译 2 选项-B 或--aluays-make。也可以通过刷新所有源文件的时间戳,间接达到重新编译出一个新Nginx可执行程序的目的。

[root@localhost nginx-1.2.0]# find .-name "*.c" | xargs touch

不直接使用make clean 是因为执行它会把objs 整个目录都删除,当然这也包括我们修改过的objs/Makefile文件。获得正常编译后的Nginx二进制可执行程序后,我们可以利用gdb调试它,不过这首先需要把 Nginx 运行起来。在默认情况下,Nginx 会有多个进程,所以需通过如下类似命令正确找到我们要调试的进程。

[root@localhost ~]# ps -efH | grep nginx

root 3971 24701 0 12:20 pts/4 00:00:00 grep nginx [root@localhost nginx-1.2.0]# make -B

root 3905 1 0 12:16 ? 00:00:00 nginx: master process ./nginx

nobody 3906 3905 0 12:16 ? 00:00:00 nginx: worker process

nobody 3907 3905 0 12:16 ? 00:00:00 nginx: worker process

源码实现已经给Nginx进程加上了title,所以根据标题很容易区分出哪个是监控进程,哪些个是工作进程。如要对如上所示的工作进程3906进行gdb调试,那么可以利用gdb的-p命令行参数。

[root@localhost ~]# gdb -p 3906

或者执行gdb命令进入gdb后执行。

(gdb) attach 3906

这两种方法都可以。

如果是要调试 Nginx 对客户端发过来请求的处理过程,那么要注意请求是否被交付给另外一个工作进程处理而导致绑定到 gdb 的这个工作进程实际没有动作。此时可以考虑开两个终端,运行两个gdb分别attach到两个工作进程上或干脆修改配置项worker_processes的值为1,从而使得Nginx只运行一个工作进程。

worker_processes 1;

通过上面这种方法只能调试 Nginx 运行起来之后的流程,对于启动过程中的逻辑,比如进程创建、配置解析等,因为已经执行完毕而无法调试,要调试这部分逻辑必须在 Nginx 启动的开始就把 gdb 绑定上,也就是在 gdb 里启动 Nginx。这有几点需要注意,首先是 Nginx默认以daemon形式运行,即它会调用fork()创建子进程并且把父进程直接exit(0)丢弃,因此在启动Nginx前,我们需设定

set follow-fork-mode child

也就是让gdb跟踪fork()之后的子进程,而gdb默认将跟踪fork()之后的父进程,不做此设定则将导致跟踪丢失。即便做了这样的设置,仍然比较麻烦,因为Nginx创建工作进程也用的是fork()函数,所以如果要调试监控进程则还需要做另外的灵活处理。我们可以修改Nginx配置文件。

daemon off;

这样Nginx就不再以daemon形象执行,利用gdb可以从Nginx的main()函数开始调试,默认情况下调试的当然就是监控进程的流程,如果要调试工作进程的流程需要在进入 gdb 后执行 setfollow-fork-modechild,在刚才已经提到了该条gdb命令的作用。另外更简单的方法就是直接设置:

master_process off;

将监控进程逻辑和工作进程逻辑全部合在一个进程里。不管怎样做,我们都必须让gdbattach到想要调试的对应进程上,比如如果必须要经过多次 fork()后才能达到的代码位置(像函数ngx_cache_manager_process_cycle()),那么就要在多处恰当位置下断点,然后在执行到该断点时根据需要切换follow-fork-mode标记。这些变通设置对于调试像配置信息解析流程、文件缓存等这一类初始相关逻辑是非常重要的,因为Nginx的这些逻辑是在Nginx启动时进行的。如果你发现gdb跟丢了进程或当前调试的代码不是你预想的流程,那么请仔细做这些确认与检查工作。

最后,因为执行Nginx需指定配置文件路径,如何在gdb里带参数运行Nginx是必须知道的。这有很多种方法,比如在Shell里执行:

gdb --args ./objs/nginx -c /usr/local/nginx/conf/nginx.conf

进入到gdb后在执行r命令即可;或者在Shell里执行:

gdb ./objs/nginx

进入到gdb 后执行r -c /usr/local/nginx/conf/nginx.conf或在gdb内先执行命令

set args -c /usr/local/nginx/conf/nginx.conf

再执行r命令。

2.1.2 gdb的watch指令

将 Nginx特定进程绑定到 gdb 后,剩余的跟踪与调试操作无非就是 gdb 的使用,这可以参考官方手册。手册内容很多,因为 gdb 提供的功能非常丰富,但平常我们使用的功能却很少。其实gdb 的某些功能是相当有利用价值的,像Break conditions、Watchpoints 等。这里仅以Watchpoints(监视点)为例看看它的实际使用效果。Watchpoints可以帮助我们监视某个变量在什么时候被修改,这对于我们了解Nginx程序的执行逻辑非常有帮助。比如在理解Nginx的共享内存逻辑时,看到ngx_shared_memory_add()函数内初始化的shm_zone->init回调为空。

1256:代码片段2.1.2-1,文件名: ngx_cycle.c

1257:ngx_shm_zone_t *

1258:ngx_shared_memory_add(ngx_conf_t *cf, ngx_str_t *name, size_t size, void *tag)

1259:{

1260:…

1318: shm_zone->init = NULL;

而在 ngx_init_cycle()函数里对该回调函数却是直接执行而并没有做前置判空处理。

41: 代码片段2.1.2-2,文件名: ngx_cycle.c

42: ngx_cycle_t *

43: ngx_init_cycle(ngx_cycle_t *old_cycle)

44: {

45: …

475:  if (shm_zone[i].init(&shm_zone[i], NULL) != NGX_OK) {

476:   goto failed;

477:  }

这说明这个函数指针一定是在其他某处被再次赋值,但具体是在哪里呢?搜索 Nginx 全部源代码可能一下子没找到对应的代码行,那么,此时就可利用gdb的Watchpoints功能进行快速定位。

(gdb) b ngx_cycle.c:1318

Breakpoint 1 at 0x805d7ce: file src/core/ngx_cycle.c, line 1318.

(gdb) r

Starting program: /home/gqk/nginx-1.2.0/objs/nginx -c /usr/local/nginx/conf/ nginx.conf.upstream.sharedmem

[Thread debugging using libthread_db enabled]

Breakpoint 1, ngx_shared_memory_add (cf=0xbffff39c, name=0xbfffeed8, size=134217728, tag=0x80dbd80) at src/core/ngx_cycle.c:1318

1318 shm_zone->init = NULL;

Missingseparatedebuginfos,use:debuginfo-installglibc-2.12-1.47.el6.i686nss-softokn-freebl-3.12.9-11.el6.i686openssl-1.0.0-20.el6.i686pcre-7.8-3.1.el6.i686zlib-1.2.3-27.el6.i686

(gdb) p &shm_zone->init

$1 = (ngx_shm_zone_init_pt *) 0x80eba68

(gdb) watch *(ngx_shm_zone_init_pt *) 0x80eba68

Hardware watchpoint 2: *(ngx_shm_zone_init_pt *) 0x80eba68

(gdb) c

Continuing.

Hardware watchpoint 2: *(ngx_shm_zone_init_pt *) 0x80eba68

Old value = (ngx_shm_zone_init_pt) 0

New value = (ngx_shm_zone_init_pt) 0x809d9c7 <ngx_http_file_cache_init>

ngx_http_file_cache_set_slot (cf=0xbffff39c, cmd=0x80dc0d8, conf=0x0) at src/http/ngx_http_file_cache.c:1807

1807 cache->shm_zone->data = cache;

先在 shm_zone->init = NULL;代码所对应的第1318 行先下一个 Breakpoint,执行 Nginx后将在此处暂停程序,通过 p 指令打印获取 shm_zone->init 的地址值,然后直接给shm_zone->init对应的地址下个Watchpoint进行监视。这样即便是跑出shm_zone->init变量所在的作用域也没有关系,执行c命令继续执行Nginx,一旦shm_zone->init被修改,那么就停止在进行修改的代码的下一行,修改之前的值Old value 和修改之后的值New value也将都被gdb抓取出来。如上示例中,可以看到修改逻辑在第1806行(我这里是以proxy_cache所用的共享内存作为实例,而在其他实例情况下,可能将与此不同)。

1084:代码片段2.1.3-1,文件名: ngx_http_file_cache.c

1085:…

1086: cache->shm_zone->init = ngx_http_file_cache_init;

1087: cache->shm_zone->data = cache;

从上面的简单示例里可以看到gdb watch命令的强大作用,除了利用该命令监控指定变量的写操作以外,还可以利用另外两个同类命令rwatch和awatch分别监控指定变量的读操作和读/写操作。当然,关于这方面的更多内容,在gdb手册上有详细介绍[4]

2.1.3 Nginx对gdb的支持

Nginx本身对于gdb也有相关辅助支持,这表现在配置指令debug_points上,对于该配置2.1 利用gdb调试项的配置值可以是stop或abort。当Nginx遇到严重错误时,比如内存超限或其他不可预料的逻辑错误,就会调用 ngx_debug_point()函数(这类似于 assert()一样的断言函数,只是函数ngx_debug_point()本身不带判断),该函数根据debug_points配置指令的设置做出相应的处理。如果将debug_points设置为stop,那么ngx_debug_point()函数的调用将使得Nginx进程进入到暂停状态,以便我们可通过gdb接入到该进程查看相关上下文信息。

[root@localhost ~]# ps aux | grep nginx

root 4614 0.0 0.0 24044 592 ?  Ts 12:48 0:00 ./nginx

root 4780 0.0 0.1 103152 800 pts/4 S+ 13:00 0:00 grep nginx

注意上面的./nginx 状态为Ts(s 代表Nginx 进程为一个会话首进程session leader),其中T就代表Nginx进程处在TASK_STOPPED状态,此时我们用gdb连上去即可查看问题所在(我这里只是一个测试,在main函数里主动调用ngx_debug_point()而已,所以下面看到的bt堆栈很简单,实际使用时,我们当然要把该函数放在需要观察的代码点,比如非正常逻辑点)。

[root@localhost ~]# gdb -q -p 4614

Attaching to process 4614

Reading symbols from /usr/local/nginx/sbin/nginx...done.

...

openssl-1.0.0-4.el6.x86_64 pcre-7.8-3.1.el6.x86_64 zlib-1.2.3-25.el6.x86_64

(gdb) bt

#0 0x0000003a9ea0f38b in raise () from /lib64/libpthread.so.0

#1 0x0000000000431a8a in ngx_debug_point () at src/os/unix/ngx_process.c:603

#2 0x00000000004035d9 in main (argc=1, argv=0x7fffbd0a0c08) at src/core/ nginx.c:406(gdb) c

Continuing.

Program received signal SIGTERM, Terminated.

执行c命令,Nginx即自动退出。

如果将debug_points设置为abort,那么Nginx调用ngx_debug_point()函数时直接将程序abort崩溃掉,如果对操作系统做了恰当的设置,则将获得对应的core文件,这就大大方便我们进行事后的慢慢调试,延用上面的直接在main函数里主动调用ngx_debug_point()的例子。

[root@localhost nginx]# ulimit -c

0

[root@localhost nginx]# ulimit -c unlimited

[root@localhost nginx]# ulimit -c

unlimited

[root@localhost nginx]# ./sbin/nginx

[root@localhost nginx]# ls

client_body_temp core.5242 html proxy_temp scgi_temp

conf   fastcgi_temp logs sbin  uwsgi_temp

生成了名为 core.5242的 core文件,利用 gdb调试该 core文件。

[root@localhost nginx]# gdb sbin/nginx core.5242 -q

Reading symbols from /usr/local/nginx/sbin/nginx...done.

[New Thread 5242]

...

(gdb) bt

#0 0x0000003a9de329a5 in raise () from /lib64/libc.so.6

#1 0x0000003a9de34185 in abort () from /lib64/libc.so.6

#2 0x0000000000431a92 in ngx_debug_point () at src/os/unix/ngx_process.c:607

#3 0x00000000004035d9 in main (argc=1, argv=0x7fffd5625f18) at src/core/ nginx.c:406(gdb) up 3

#3 0x00000000004035d9 in main (argc=1, argv=0x7fffd5625f18) at src/core/nginx.c:406

406  ngx_debug_point();

(gdb) list

401  }

402  }

403

404  ngx_use_stderr = 0;

405

406  ngx_debug_point();

407

408  if (ngx_process == NGX_PROCESS_SINGLE) {

409   ngx_single_process_cycle(cycle);

410

2.1.4 宏

Nginx 里有大量的宏。如果不事先做一下处理,在 gdb 里将无法查看这些宏的定义以及展开形式,也就会获得如下提示信息。

(gdb) info macro NGX_OK

The symbol 'NGX_OK' has no definition as a C/C++ preprocessor macro

at <user-defined>:-1

(gdb) p NGX_OK

No symbol "NGX_OK" in current context.

如果我们将编译选项-g 改为-ggdb3,虽然这样编译得到的二进制文件会比较大,但是因为它包含了所有与宏相关的信息(当然也包含了很多其他信息),所以我们就可以在gdb里使用类似命令。

(gdb) info macro NGX_OK

Defined at src/core/ngx_core.h:30

included at src/core/nginx.c:9

#define NGX_OK 0

(gdb) macro expand NGX_OK

expands to: 0

来查看指定宏的定义与展开形式,而gdb命令里也可以直接使用这些宏,比如执行打印指令p。

(gdb) p NGX_OK

$1 = 0

当然,这些操作需要在当前上下文里有对应的NGX_OK宏定义,否则同样无法查看。这很容易理解,毕竟宏也有对应的“作用域”,也就是说同一个宏名在不同的代码处可能有不同的展开,所以gdb是利用当前代码列表作为选择“作用域”的参考点。

如果当前应用程序在执行当中,比如在main()函数处下断点,然后执行r命令后被断了下来,那么当前代码列表就是以main函数里的第一行作为参考点,宏展开也就以当前执行行作为参考点。如果应用程序当前未处于执行状态,并且也没有使用 list 命令指定当前代码行,那么宏可能无法显示或显示不正确。比如我在Nginx的main()函数处查看EPOLLIN宏,结果如下。

(gdb) info macro EPOLLIN

The symbol `EPOLLIN' has no definition as a C/C++ preprocessor macro

at <user-defined>:-1

结果表明没有找到EPOLLIN宏,但如果我使用 list命令列表,会使用到EPOLLIN宏的源文件,那么对应的情况如下。

(gdb) list ngx_epoll_module.c:0

1

2 /*

3  * Copyright (C) Igor Sysoev

4  * Copyright (C) Nginx, Inc.

5  */

6

7

8 #include <ngx_config.h>

9 #include <ngx_core.h>

10 #include <ngx_event.h>

(gdb) info macro EPOLLIN

Defined at /usr/include/sys/epoll.h:47

included at src/os/unix/ngx_linux_config.h:86

included at src/core/ngx_config.h:26

included at src/event/modules/ngx_epoll_module.c:8

#define EPOLLIN EPOLLIN

可以看到第二次info macro就能正确找到并显示EPOLLIN宏了。关于这方面的更多实例,请参考这里[5]

2.1.5 cgdb

cgdb[6]是我想推荐给大家使用的一个封装gdb的开源调试工具。相比Windows下的Visual Studio等图形调试工具而言,它的可视化功能显得十分轻量级,但它的最大好处在于能在终端里运行并且原生具备gdb的强大调试功能。关于cgdb的详细使用可以参考官方手册[7]或这里[8]

cgdb在远程ssh里执行的界面如图2-1所示,如果上面类vi窗口没有显示对应的源代码或下面gdb 窗口提示Nosuch fileordirectory.,那么需要利用directory命令把Nginx 源代码增加到搜索路径。

图2-1 cgdb运行时界面

2.2 利用日志信息跟踪Nginx

优秀的程序都会带有自己的日志输出接口,并且一般还会给出不同等级的输出级别,以便于重次信息的过滤,比如Linux内核的日志输出标准接口为printk,并且给出了KERN_EMERG、KERN_ALERT、KERN_DEBUG等这样的输出等级。Nginx与此类似,下面具体来看。

为了获取最丰富的日志信息,我们在进行configure配置时,需要把--with-debug选项加上,这样能生成一个名为NGX_DEBUG的宏,而在Nginx源码内,该宏被用作控制开关,如果没有它,那么很多日志逻辑代码将在 make 编译时直接跳过。比如对单连接的 debug_connection调试指令、分模块日志调试debug_http功能等。

00: 代码片段2.2-1,文件名: ngx_auto_config.h

01: #define NGX_CONFIGURE " --with-debug"

02:

03: #ifndef NGX_DEBUG

04: #define NGX_DEBUG 1

05: #endif

620: 代码片段2.2-2,文件名: nginx.c

621: #if (NGX_DEBUG)

622:  {

623:  char **e;

624:  for (e = env; *e; e++) {

625:   ngx_log_debug1(NGX_LOG_DEBUG_CORE, cycle->log, 0, "env: %s", *e);

626:  }

627:  }

628: #endif

有了上面这个编译前提条件之后,我们还需在配置文件里做恰当的设置。关于这点,Nginx提供的主要配置指令为error_log。该配置项的默认情况(默认值定义在objs/ngx_auto_config.h文件内)为

error_log logs/error.log error;

表示日志信息记录在logs/error.log(如果没改变Nginx的默认工作路径的话,那么其父目录为/usr/local/nginx/)文件内,而日志记录级别为error。

在实际进行配置时,可以修改日志信息记录文件路径(比如修改为/dev/null,此时所有日志信息将被输出到所谓的 Linux 黑洞设备,导致日志信息全部丢弃)或直接输出到标准终端(此时指定为stderr)。Nginx提供的日志记录级别一共有八级,等级从低到高分别为debug、info、notice、warn、error、crit、alert、emerg。如果设置为error,则表示Nginx内等级为error、crit、alert和emerg的四种级别的日志将被输出到日志文件或标准终端。另外的debug、info、notice和warn这四种日志将被直接过滤掉而不会输出。因此如果我们只关注特别严重的信息,只需将日志等级设置为error即可大大减少Nginx的日志输出量,这样就避免了在大量的日志信息里寻找重要信息的麻烦。

当我们利用日志跟踪 Nginx 时,需要获取最大量的日志信息,所以此时可以把日志等级设置为最低的debug级。在这种情况下,如果觉得调试日志太多,Nginx提供按模块控制的更细粒等级:debug_core、debug_alloc、debug_mutex、debug_event、debug_http、debug_imap。比如如果只想看http的调试日志,则需做如下设置。

error_log logs/error.log debug_http;

此时Nginx将输出从info到emerg所有等级的日志信息,而debug日志则将只输出与http模块相关的内容。

error_log配置指令可以放在配置文件的多个上下文内,比如main、http、server、location,但同一个上下文中只能设置一个error_log,否则Nginx将提示类似如下这样的错误。

nginx: [emerg] "error_log" directive is duplicate in /usr/local/nginx/conf/ nginx.conf:9

但在不同的配置文件上下文里可以设置各自的error_log配置指令,通过设置不同的日志文件,这是Nginx提供的又一种信息切割过滤手段。

00: 代码片段2.2-3,文件名: example.conf

01: ...

02: error_log logs/error.log error;

03: ...

04: http {

05:  error_log logs/http.log debug;

06:  ...

07:  server {

08:   ...

09:   error_log logs/server.log debug;

10: ...

Nginx 提供的另一种更有针对性的日志调试信息记录是针对特定连接的,这通过debug_connection配置指令来设置,比如如下设置调试日志仅针对IP地址192.168.1.1和IP段192.168.10.0/24:

11: 代码片段2.2-4,文件名: example.conf

12: events {

13:  debug_connection 192.168.1.1;

14:  debug_connection 192.168.10.0/24;

15: }

Nginx 的日志功能仍在不断改进中,如能利用得好,对于我们跟踪 Nginx 还是非常有帮助的,至少我知道有不少朋友十分习惯于使用C库的printf()函数打印调试,相比之下,Nginx提供的ngx_log_xxx()系列函数要强大得多。

2.3 利用strace/pstack 调试Nginx

Linux下有两个命令strace[9]和ltrace[10]可以分别用来查看一个应用程序在运行过程中所发起的系统函数调用和动态库函数调用,这对作为标准应用程序的Nginx自然同样可用。由于这两个命令大同小异,下面就仅以strace为例做简单介绍,大致了解一些它能帮助我们获取哪些有用的调试信息。关于strace/ltrace以及后面介绍的pstack更多的用法请参考对应的Man手册。

从strace的Man手册可以看到几个有用的选项。

• -ppid:通过进程号来指定被跟踪的进程。

• -ofilename:将跟踪信息输出到指定文件。

• -f:跟踪其通过frok调用产生的子进程。

• -t:输出每一个系统调用的发起时间。

• -T:输出每一个系统调用消耗的时间。

首先利用 ps 命令查看到系统当前存在的 Nginx 进程,然后用 strace 命令的-p 选项跟踪Nginx工作进程,如图2-2所示。

图2-2 查看Nginx进程

为了简化操作,我这里只设定了一个工作进程,该工作进程会停顿在epoll_wait系统调用上,这是合理的,因为在没有客户端请求时,Nginx就阻塞于此(除非是在争用accept_mutex锁),在另一终端执行wget命令向Nginx发出http请求后,再来看strace的输出,如图2-3所示。

[root@localhost ~]# wget 127.0.0.1

图2-3 strace的输出

通过strace的输出可以看到Nginx工作进程在处理一次客户端请求过程中发起的所有系统调用。我这里测试请求的HTML非常简单,没有附带css、js、jpg等文件,所以看到的输出也比较简单。strace输出的每一行记录一次系统调用,等号左边是系统调用名以及调用参数,等号右边是该系统调用的返回值。逐一说明如下所述。

1.epoll_wait 返回值为1,表示有1 个描述符存在可读/写事件,这里当然是可读事件。

2.accept4 接受该请求,返回的数字3表示socket的文件描述符。

3.epoll_ctl 把accept4 建立的socket 套接字(注意参数3)加入到事件监听机制里。

4.recv 从发生可读事件的 socket 文件描述符内读取数据,读取的数据存在第二个参数内,读取了107个字节。

5.stat64 判断客户端请求的html 文件是否存在,返回值为0 表示存在。

6.open/fstat64 打开并获取文件状态信息。open 文件返回的文件描述符为 9,后面几个系统调用都用到这个值。

7.writev 把响应头通过文件描述符3 代表的socket套接字发给客户端。

8.sendfile64 把文件描述符9 代表的响应体通过文件描述符3 代表的socket 套接字发给客户端。

9.再往文件描述符4 代表的日志文件内write 一条日志信息。

10.recv 看客户端是否还发了其他待处理的请求/信息。

11.最后关闭文件描述符3代表的socket 套接字。

由于strace能够提供Nginx执行过程中的这些内部信息,所以在出现一些奇怪现象时,比如Nginx 启动失败、响应的文件数据和预期不一致、莫名其妙的Segment ation Fault 段错误、存在性能瓶颈(利用-T选项跟踪各个函数的消耗时间),利用strace也许能提供一些相关帮助。最后,要退出strace跟踪,按Ctrl+C即可。

命令strace跟踪的是系统调用,对于Nginx本身的函数调用关系无法给出更为明朗的信息,如果我们发现Nginx当前运行不正常,想知道Nginx当前内部到底在执行什么函数,那么命令pstack就是一个非常方便实用的工具。

pstack的使用也非常简单,后面跟进程ID即可。比如在无客户端请求的情况下,Nginx阻塞在epoll_wait系统调用处,此时利用pstack查看到的Nginx函数调用堆栈关系,如图2-4所示。

图2-4 Nginx 函数调用堆栈关系

从main()函数到epoll_wait()函数的调用关系一目了然,和在gdb内看到的堆栈信息一模一样,其实命令pstack本身也就是一个利用gdb实现的Shell脚本,关于这点,感兴趣的读者可以自己看下pstack对应的脚本程序。

2.4 获得Nginx 程序完整执行流程

利用strace命令能帮助我们获取到Nginx在运行过程中所发起的所有系统调用,但是不能看到Nginx内部各个函数的调用情况。利用gdb调试Nginx能让我们很清晰地获得Nginx每一步的执行流程,但是单步调试毕竟是非常麻烦的,有没有更为方便的方法一次性获得Nginx程序执行的整个流程呢?答案是肯定的,而且方法还非常多[11]。虽然相比直接使用某些强大工具(如System Tap[12])而言,下面要介绍的方法比较笨,但它的确可行,而且从这个过程中也许能学到一些额外的知识。我们只需利用gcc的一个名为-finstrument-functions[13]的编译选项,再加上一些我们自己的处理,就可以达到既定目的。关于gcc提供的这个-finstrument-functions选项,这里不做过多介绍,我们只需明白它提供的是一种函数调用记录追踪功能。关于这些,感兴趣的读者请直接参考gcc官网手册,下面来看获得Nginx程序完整执行流程的具体操作。

首先,我们准备两个文件,文件名和文件内容分别如下。

00: 代码片段2.4-1,文件名: my_debug.h

01: #ifndef MY_DEBUG_LENKY_H

02: #define MY_DEBUG_LENKY_H

03: #include <stdio.h>

04:

05: void enable_my_debug( void ) __attribute__((no_instrument_function));

06: void disable_my_debug( void ) __attribute__((no_instrument_function));

07: int get_my_debug_flag( void ) __attribute__((no_instrument_function));

08: void set_my_debug_flag( int ) __attribute__((no_instrument_function));

09: void main_constructor( void ) __attribute__((no_instrument_function, constructor));

10: voidmain_destructor(void) __attribute__((no_instrument_function,destructor));

11: void __cyg_profile_func_enter(void *,void *) __attribute__((no_instrument_function));

12: void __cyg_profile_func_exit( void *, void *) __attribute__((no_instrument_ function));

13:

14: #ifndef MY_DEBUG_MAIN

15: extern FILE *my_debug_fd;

16: #else

17: FILE *my_debug_fd;

18: #endif

19: #endif

00: 代码片段2.4-2,文件名: my_debug.c

01: #include "my_debug.h"

02: #define MY_DEBUG_FILE_PATH "/usr/local/nginx/sbin/mydebug.log"

03: int _flag = 0;

04:

05: #define open_my_debug_file() \

06:  (my_debug_fd = fopen(MY_DEBUG_FILE_PATH, "a"))

07:

08: #define close_my_debug_file() \

09:  do { \

10:   if (NULL != my_debug_fd) { \

11:    fclose(my_debug_fd); \

12:   } \

13:  }while(0)

14:

15: #define my_debug_print(args, fmt...) \

16:  do{ \

17:   if (0 == _flag) { \

18:    break; \

19:   } \

20:   if (NULL == my_debug_fd && NULL == open_my_debug_file()) { \

21:    printf("Err: Can not open output file.\n"); \

22:    break; \

23:   } \

24:   fprintf(my_debug_fd, args, ##fmt); \

25:   fflush(my_debug_fd); \

26:  }while(0)

27:

28: void enable_my_debug( void )

29: {

30:  _flag = 1;

31: }

32: void disable_my_debug( void )

33: {

34:  _flag = 0;

35: }

36: int get_my_debug_flag( void )

37: {

38:  return _flag;

39: }

40: void set_my_debug_flag( int flag )

41: {

42:  _flag = flag;

43: }

44: void main_constructor( void )

45: {

46:  //Do Nothing

47: }

48: void main_destructor( void )

49: {

50:  close_my_debug_file();

51: }

52: void __cyg_profile_func_enter( void *this, void *call )

53: {

54:  my_debug_print("Enter\n%p\n%p\n", call, this);

55: }

56: void __cyg_profile_func_exit( void *this, void *call )

57: {

58:  my_debug_print("Exit\n%p\n%p\n", call, this);

59: }

这两个文件是我2009年写的,比较乱,不过够用且测试无误,所以我这里也就直接先用它。将这两个文件放到/nginx-1.2.0/src/core/目录下,然后编辑/nginx-1.2.0/objs/Makefile文件,给CFLAGS选项增加-finstrument-functions选项。

02: 代码片段2.4-3,文件名: Makefile

03: CFLAGS = -pipe -O0 -W -Wall -Wpointer-arith -Wno-unused-parameter -Wunused-function -Wunused-va  riable -Wunused-value -Werror -g -finstrument-functions

接着,需要将my_debug.h和my_debug.c引入到Nginx源码里一起编译,所以继续修改/nginx-1.2.0/objs/Makefile文件,根据Nginx的Makefile文件特点,修改的地方主要有如下几处。

00: 代码片段2.4-4,文件名: Makefile

01: …

18: CORE_DEPS = src/core/nginx.h \

19:  src/core/my_debug.h \

20: …

84: HTTP_DEPS = src/http/ngx_http.h \

85:  src/core/my_debug.h \

86: …

102: objs/nginx: objs/src/core/nginx.o \

103:   objs/src/core/my_debug.o \

104: …

211:   $(LINK) -o objs/nginx \

212:   objs/src/core/my_debug.o \

213: …

322: objs/src/core/my_debug.o: $(CORE_DEPS) \

323:   src/core/my_debug.c

324:   $(CC) -c $(CFLAGS) $(CORE_INCS) \

325:     -o objs/src/core/my_debug.o \

326:     src/core/my_debug.c

327: …

为了在 Nginx 源码里引入 my_debug,这需要在 Nginx 所有源文件都包含有头文件 my_debug.h,当然没必要每个源文件都去添加对这个头文件的引入,我们只需要在头文件ngx_core.h内加入对my_debug.h文件的引入即可,这样其他Nginx的源文件就间接地引入了这个文件。

37: 代码片段2.4-5,文件名: ngx_core.h

38: #include "my_debug.h"

在源文件nginx.c的最前面加上对宏MY_DEBUG_MAIN的定义,以使得Nginx程序有且仅有一个my_debug_fd变量的定义。

06: 代码片段2.4-6,文件名: nginx.c

07: #define MY_DEBUG_MAIN 1

08:

09: #include <ngx_config.h>

10: #include <ngx_core.h>

11: #include <nginx.h>

最后就是根据我们想要截取的执行流程,在适当的位置调用函数enable_my_debug();和函数 disable_my_debug();,这里仅作测试,直接在 main 函数入口处调用 enable_my_debug();,而disable_my_debug();函数就不调用了。

200: 代码片段2.4-7,文件名: nginx.c

201: main(int argc, char *const *argv)

202: {

203: …

208: enable_my_debug();

至此,代码增补工作已经完成,重新编译Nginx,如果之前已编译过Nginx,那么需记得先把Nginx源文件的时间戳进行刷新。

以单进程模式运行 Nginx,并且在配置文件里将日志功能的记录级别设置低一点,否则将有大量的日志函数调用堆栈信息,经过这样的设置后,我们才能获得更清晰的 Nginx 执行流程,即配置文件里做如下设置。

00: 代码片段2.4-8,文件名: nginx.c

01: master_process off;

02: error_log logs/error.log emerg;

正常运行后的Nginx将产生一个记录程序执行流程的文件,这个文件会随着Nginx的持续运行迅速增大,所以在恰当的地方调用 disable_my_debug();函数是非常有必要的,不过我这里在获取到一定量的信息后就直接kill掉Nginx进程了。mydebug.log的内容如下所示。

[root@localhost sbin]# head -n 20 mydebug.log

Enter

0x804a5fc

0x806e2b3

Exit

0x804a5fc

0x806e2b3

这记录的是Nginx执行函数调用关系,不过这里的函数还只是以对应的地址显示而已,利用另外一个工具 addr2line 可以将这些地址转换回可读的函数名。addr2line 工具在大多数Linux发行版上默认有安装,如果没有那么在官网[14]下载即可,其具体用法也可以参考官网手册[15]。这里我们直接使用,写个addr2line.sh脚本。

00: 代码片段2.4-9,文件名: addr2line.sh

01: #!/bin/sh

02:

03: if [ $# != 3 ]; then

04:  echo 'Usage: addr2line.sh executefile addressfile functionfile'

05:  exit

06: fi;

07:

08: cat $2 | while read line

09: do

10:  if [ "$line" = 'Enter' ]; then

11:   read line1

12:   read line2

13: #  echo $line >> $3

14:   addr2line -e $1 -f $line1 -s >> $3

15:   echo "--->" >> $3

16:   addr2line -e $1 -f $line2 -s | sed 's/^/ /' >> $3

17:   echo >> $3

18:  elif [ "$line" = 'Exit' ]; then

19:   read line1

20:   read line2

21:   addr2line -e $1 -f $line2 -s | sed 's/^/ /' >> $3

22:   echo "<---" >> $3

23:   addr2line -e $1 -f $line1 -s >> $3

24: #  echo $line >> $3

25:   echo >> $3

26:  fi;

27: done

执行addr2line.sh进行地址与函数名的转换,这个过程挺慢的,因为从上面的Shell脚本可以看到对于每一个函数地址都调用addr2line进行转换,执行效率完全没有考虑,不过够用就好,如果非要追求高效率,直接写个C程序来做这个转换工作当然也是可以的。

[root@localhost sbin]# vi addr2line.sh

[root@localhost sbin]# chmod a+x addr2line.sh

[root@localhost sbin]# ./addr2line.sh nginx mydebug.log myfun.log

[root@localhost sbin]# head -n 12 myfun.log

main

nginx.c:212

--->

ngx_strerror_init

ngx_errno.c:47

ngx_strerror_init

ngx_errno.c:47

<---

main

nginx.c:212

关于如何获得 Nginx 程序执行流程的方法大体就是上面描述的这样,不过这里介绍得很粗略,写的代码也仅只是作为示范使用,关于 gcc 以及相关工具的更深入研究已不在本书的讨论范围之内,如感兴趣可查看上文中提供的相关链接。

2.5 加桩调试

如果我们对代码做过单元测试,那么肯定知道加桩的概念,简单点说就是为了让一个模块执行起来,额外添加的一些支撑代码。比如,我要简单测试一个实现某种排序算法的子函数的功能是否正常,那么我也许需要写一个 main()函数,设置一个数组,提供一些乱序的数据,然后利用这些数据调用排序子函数(假设它提供的接口就是对数组的排序),然后 printf打印排序后的结果,看是否排序正常,所有写的这些额外代码(main()函数、数组、printf打印)就是桩代码。

上面提到的这种用于单元测试的方法,同样也可以用来深度调试 Nginx 内部逻辑,而且Nginx很多的基础实现(比如slab机制、红黑树、chain链、array数组等)都比较独立,要调试它们只需提供少量的桩代码即可。

以Nginx的slab机制为例,我们通过下面所提供的这些桩代码即可调试该功能的具体实现。Nginx 的 slab 机制用于对多进程共享内存的管理,不过单进程也是一样的执行逻辑,除了加/解锁直通以外(即加锁时必定成功),所以我们采取最简单的办法,直接在 Nginx 本身的main()函数内插入我们的桩代码。当然,必须根据具体情况把桩代码放在合适的调用位置,比如这里的slab机制就依赖一些全局变量(像ngx_pagesize等),所以需要把桩代码的调用位置放在这些全局变量的初始化之后。

197: 代码片段2.5-1,文件名: nginx.c

198: void ngx_slab_test()

199: {

200:  ngx_shm_t shm;

201:  ngx_slab_pool_t *sp;

202:  u_char *file;

203:  void *one_page;

204:  void *two_page;

205:

206:  ngx_memzero(&shm, sizeof(shm));

207:  shm.size = 4 * 1024 * 1024;

208:  if (ngx_shm_alloc(&shm) != NGX_OK) {

209:   goto failed;

210:  }

211:

212:  sp = (ngx_slab_pool_t *) shm.addr;

213:  sp->end = shm.addr + shm.size;

214:  sp->min_shift = 3;

215:  sp->addr = shm.addr;

216:

217: #if (NGX_HAVE_ATOMIC_OPS)

218:  file = NULL;

219: #else

220:  #error must support NGX_HAVE_ATOMIC_OPS.

221: #endif

222:  if (ngx_shmtx_create(&sp->mutex, &sp->lock, file) != NGX_OK) {

223:   goto failed;

224:  }

225:

226:  ngx_slab_init(sp);

227:

228:  one_page = ngx_slab_alloc(sp, ngx_pagesize);

229:  two_page = ngx_slab_alloc(sp, 2 * ngx_pagesize);

230:

231:  ngx_slab_free(sp, one_page);

232:  ngx_slab_free(sp, two_page);

233:

234:  ngx_shm_free(&shm);

235:

236:  exit(0);

237: failed:

238:  printf("failed.\n");

239:  exit(-1);

240: }

241: …

353:  if (ngx_os_init(log) != NGX_OK) {

354:   return 1;

355:  }

356:

357:  ngx_slab_test();

358: …

上面是修改之后的nginx.c源文件,直接make后生成新的Nginx,不过这个可执行文件不再是一个Web服务器,而是一个简单的调试slab机制的辅助程序。可以看到,程序在进入main()函数后先做一些初始化工作,然后通过 ngx_slab_test()函数调入到桩代码内执行调试逻辑,完成既定目标后便直接exit()退出整个程序。

正常运行时,Nginx 本身对内存的申请与释放是不可控的,所以直接去调试 Nginx 内存管理的 slab 机制的相关代码逻辑非常困难,利用这种加桩的办法,ngx_slab_alloc()申请内存和ngx_slab_free()释放内存都能精确控制,对每一次内存的申请或释放后,slab机制的内部结构发生了怎样的变化都能准确地掌握,对其相关逻辑的理解也就没有那么困难了。

2.6 特殊应用逻辑的调试

前面所讲的调试方法都是针对 Nginx 本身很容易跑到的逻辑,而对于某些只有在特定情况下才会被执行到的代码,又该怎样去调试呢?举个例子,我们知道 Nginx 里有大量的超时处理,比如,如果读取客户端请求头部数据超时,Nginx 就将执行对应的超时处理函数,假设我想通过单步执行的方式来了解这部分相关逻辑,无疑就得让 Nginx 的执行逻辑走到这条路径上来。由于此时影响 Nginx 行为的决定因素是客户端所发送的请求头部数据,我们就必须在客户端做动作来构造出这种场景。一般的浏览器,如IE、Firefox等发出请求的行为基本已经固定,而常用的命令行工具,比如 curl、wget 的源代码又略显复杂,定制它们的请求动作和改变环境来构造所需的场景相对较为麻烦,所以一种更便利的方法就是我们自己写个socket通信的客户端即可,而这并不需要多少代码。

下面给出一个测试示例用代码,为了简单,所以服务器IP和端口都是固定在代码里的,用于发送数据的函数write()调用也未做返回值判断等(后续还有其他类似测试代码也是如此,这点请注意)。

00: 代码片段2.6-1,文件名: request_timeout.c

01: /**

02: * gcc -Wall -g -o request_timeout request_timeout.c

03: */

04: #include <sys/types.h>

05: #include <stdio.h>

06: #include <stdlib.h>

07: #include <string.h>

08: #include <errno.h>

09: #include <sys/socket.h>

10: #include <netinet/in.h>

11: #include <arpa/inet.h>

12: #include <unistd.h>

13:

14: //charreq_header[]="GET/HTTP/1.1\r\nUser-Agent:curl/7.19.7\r\nHost:127.0.0.1\r\nAccept: */*\r\n\r\n";

15: char req_header[] = "GET / HTTP/1.1\r\nUser-Agent: curl/7.19.7\r\n";

16:

17: int main(int argc, char *const *argv)

18: {

19:  int sockfd;

20:  struct sockaddr_in server_addr;

21:

22:  if ((sockfd = socket (AF_INET, SOCK_STREAM, 0)) == -1) {

23:   fprintf (stderr, "Socket error,%s\r\n", strerror (errno));

24:   return -1;

25:  }

26:

27:  bzero (&server_addr, sizeof (server_addr));

28:  server_addr.sin_family = AF_INET;

29:  server_addr.sin_port = htons (80);

30:

31:  if(!inet_aton("192.168.1.1", &server_addr.sin_addr)) {

32:   fprintf (stderr, "Bad address:%s\r\n", strerror (errno));

33:   close (sockfd);

34:   return -1;

35:  }

36:

37:  if (connect (sockfd, (struct sockaddr *) (&server_addr),

38:   sizeof (struct sockaddr)) == -1) {

39:   fprintf (stderr, "Connect Error:%s\r\n", strerror (errno));

40:   close (sockfd);

41:   return -1;

42:  }

43:

44:  write (sockfd, req_header, strlen(req_header));

45:

46:  close (sockfd);

47:  return 0;

48: }

该程序的代码比较简单,变量req_header存储的是http请求头部数据,被注释掉的是正常的请求头,而我这里使用的请求头是不完整的(正常请求头可以用wget、curl或wireshark[16]等工具获得,异常请求头必须根据自己所预期场景来进行构造,比如在这里,其他异常情况的请求头可能导致Nginx以其他错误方式返回而不是进行超时监控),所以这会使得Nginx在接收到该请求后,持续等待进一步的头部数据,直到超时。编译这个源代码得到应用程序request_timeout。

将接受http请求的Nginx工作进程绑定到gdb,然后在超时函数ngx_event_expire_timers()内的第149行下断点并按c继续。

75: 代码片段2.6-2,文件名: ngx_event_timer.c

76: void

77: ngx_event_expire_timers(void)

78: {

79: …

147:    ev->timedout = 1;

148:

149:    ev->handler(ev);

这个断点是Nginx已经捕获到超时事件,设置其超时旗标并调用对应的回调函数进行处理。在另一个gdb内执行request_timeout,当然,我们需要让它停止在第47行[17],避免程序退出,导致它与Nginx工作进程之间的连接断开。等待约 60 秒(Nginx读取请求头部数据的默认超时时间为60秒,可通过配置指令client_header_timeout修改)后,attach到Nginx工作进程的gdb就会断下来,按s跟进函数,再顺着执行路径而下就会发现此时Nginx将执行到这个逻辑里。

955: 代码片段2.6-3,文件名: ngx_event_timer.c

956: static void

957: ngx_http_process_request_headers(ngx_event_t *rev)

958: {

959: …

976:  if (rev->timedout) {

977:   ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out");

978:   c->timedout = 1;

979:   ngx_http_close_request(r, NGX_HTTP_REQUEST_TIME_OUT);

980:   return;

981:  }

将执行到第976行的if判断内部,即连接超时,我们看到对于在读取请求头部数据超时的情况下,Nginx 工作进程最后所做的几步主要工作,即日志记录、关闭请求并返回。通过这样一个实例,我们也就了解了如何去调试这样的特殊应用逻辑,不仅仅只是针对客户端,对于后端应用服务器也能如此进行模拟构造。

上面演示的环境构造步骤,虽然比较简单且能真实模拟,但毕竟需要我们了解它的细节,也就是需知道触发这种情况的前提条件,如果前提条件比较多,那么模拟起来可能还是比较麻烦,其实,如果我们只是了解一下 Nginx 如果这样执行会怎么样,那么完全可以通过利用gdb的p命令或set命令修改对应条件变量的值来达到目的。比如在前面的例子里,在一般情况下,rev->timedout为0,即不超时而无法执行第977-980行代码,但我又想看一下执行这几条语句的情况会怎么样,那么就可以像下面这样做。

Breakpoint 1, ngx_http_process_request_headers (rev=0x94a6bfc) at src/http/ ngx_http_request.c:976

976  if (rev->timedout) {

(gdb) p rev->timedout

$1 = 0

(gdb) p rev->timedout=1

$2 = 1

(gdb) n

977   ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out");

(gdb) set rev->timedout=0

(gdb) p rev->timedout

$3 = 0

(gdb)

通过执行“prev->timedout=1”把变量rev->timedout的值改为1,这样就执行到第977 行了,当然,如上所示,set命令也可以改变Nginx执行变量的值。值得特别注意的是,这样做仅仅只是因为改变了条件判断的变量值而使得 Nginx 程序执行路径发生变化,但是其在新的路径上,可能由于使用的某些变量值不是原本所期望的情况而导致执行异常。

注 释

[1].http://www.gnu.org/software/gdb/documentation/。

[2].默认情况下,应该是已启用-g选项的。

[3].执行命令".lconfigure--help"可以看到所有参数选项。

[4].http://sourceware.org/gdb/current/onlinedocs/gdb/Set-Watchpoints.html#Set-Watchpoints

[5].http://lenky.info/?p=1910

[6].http://cgdb.github.com/

[7].http://cgdb.github.com/docs/index.html

[8].http://lenky.info/?p=1409

[9].http://sourceforge.net/projects/strace/

[10].http://www.ltrace.org/

[11].http://lenky.info/?p=2202

[12].http://sourceware.org/sysemtop/

[13].http://gcc.gnu.org/onlinedocs/gcc/Code-Gen-Options.html#Code-Gen-Options

[14].http://sourceware.org/binutils/

[15].http://sourceware.org/binutils/docs/binutils/addr2line.html。

[16].http://www.wireshark.org/。

[17].在此行加个gdb断点进行暂停或在该行后利用sleep()函数进行暂停都可以。

图书在版编目(CIP)数据

深入剖析Nginx/高群凯著.--北京:人民邮电出版社,2013.5

ISBN 978-7-115-30762-0

Ⅰ.①深… Ⅱ.①高… Ⅲ.①Web服务器 Ⅳ.①TP393.09

中国版本图书馆CIP数据核字(2013)第008788号

内容提要

Nginx是一款功能强大的高性能Web 和反向代理服务器,最初由俄罗斯程序员Igor Sysoev 开发,而当前由IgorSysoev领导的专业公司Nginx, Inc.进行持续的维护与更新。Nginx可以在大多数UNIX或类UNIX系统上编译运行,比如FreeBSD、Solaris、Linux等,并且官方还提供有Windows下的可执行版本。目前,Nginx在Netflix、Wordpress.com、新浪、网易、腾讯、豆瓣等国内外众多知名网站中应用。

本书不是一本关于Nginx配置指令如何使用的介绍手册。本书重点在于通过剖析Nginx的源代码,探究其功能结构及其内部实现原理。全书共14章和3个附录。首先介绍了开始剖析Nginx源代码前的准备工作,以及跟踪和调试的方法;然后,分别深入分析了Nginx的进程模型、数据结构、配置指令、主要功能模块、I/O事件处理、变量机制、客户端请求过程、Filter模块实例、负载均衡策略以及Handler模块等。附录部分提供了Nginx的编译模块、运行配置等有用信息。

从源码剖析的角度出发,是程序员常用的学习和提高方法。本书是作者多年研读Nginx代码、深入思考和不断实践的结晶。本书适合系统程序员、软件开发工程师、Nginx高级运维工程师阅读参考,对于有志从事相关工作的IT专业学生,更是不可多得的学习资料。

深入剖析Nginx

◆著 高群凯

责任编辑 陈冀康

◆人民邮电出版社出版发行  北京市崇文区夕照寺街14号

邮编 100061  电子邮件 315@ptpress.com.cn

网址 http://www.ptpress.com.cn

北京艺辉印刷有限公司印刷

◆开本:800×1000 1/16

印张:22

字数:423千字  2013年5月第1版

印数:1-3500册  2013年5月北京第1次印刷

ISBN 978-7-115-30762-0

定价:59.00元(附光盘)

读者服务热线:(010)67132692 印装质量热线:(010)67129223

反盗版热线:(010)67171154

相关图书

物联网全栈开发原理与实战
物联网全栈开发原理与实战
区块链技术本质与应用
区块链技术本质与应用
网络调研技术与实战
网络调研技术与实战
区块链架构与实现:Cosmos详解
区块链架构与实现:Cosmos详解
TongWeb中间件实用教程
TongWeb中间件实用教程
2020区块链漫游指南
2020区块链漫游指南

相关文章

相关课程