没有合适的资源?快使用搜索试试~ 我知道了~
资源推荐
资源详情
资源评论
转载请保持文档完全或保留出处(http://lenky.info/)
nginx 核心讲解《上篇》
第零章
慕名对 nginx 的源码进行学习研究是早在 2009 年的事情,当时还在学校,整天呆在实
验室里看动漫,时间一久就心感愧疚,觉得还是要趁有空学点东西,恰当时不知从哪里得
知高性能服务器是一个很有“前途”的方向,几经搜索又机缘偶合的得识 lighttpd 与 nginx,
从此开始在动漫与代码之间来回穿梭,直到毕业。
关于 lighttpd 与 nginx,无需多说,当时 lighttpd 比 nginx 要火,所以我先看的 lighttpd
源码,后看的 nginx 源码,也因此 lighttpd 的文档在我读书的时候就写完(虽然写得很矬)
了,但 nginx 的文档写了一些放在电脑里,后来离开学校开始工作后,就把这件事情和这
些文档都给搁在那了,直到近一年前,我建了一个个人博客站点(http://lenky.info/),为
了凑文章数目,才又把它们给找了出来,并且根据最新的 nginx 源码重新整理了一下,也
就是现在你看到的这篇文档。当然,这只是一部分,所以标题才叫《上篇》。
重新整理主要是因为注意到以前写的文档过细的去逐行注释代码(网上很多 nginx 源
码分析的文章也大多有这个缺点),而此次希望能从比较高一点的角度去解析 nginx,让读
者尽快的把握全局而不是陷入细节;为了达到这个目标,文档里就尽量的少贴代码多画图
当然,一些必要的代码是不可缺少的,所以你还是会在本文档里看到源代码。虽然我的个
人期望比较好,可惜水平比较差,目前写出来的文档也就这个样了。:)
最后,说一下本文档基于的相关环境,虽然列了一个表格如下,其实没那么复杂,我
安装的是一个 centos 6.2 的 32 位虚拟机,其它开发软件包都是 centos 6.2 里所对应提供的,
而 nginx 版本为 1.2.0。
软件包 版本
nginx 1.2.0
os CentOS release 6.2 (Final)/kernel-2.6.32/32bit
gcc gcc version 4.4.6 20110731 (Red Hat 4.4.6-3) (GCC)
gdb GNU gdb (GDB) Red Hat Enterprise Linux (7.2-50.el6)
make GNU Make 3.81
文档版本(更新地址:http://lenky.info/ebook/):
版本号 修订时间
0.1 2012-7-20
第一章
进程模型
nginx 的进程模型和大多数后台服务程序一样,按职责将进程分成监控进程和工作进程
欢迎访问 Lenky 个人网站:http://lenky.info/
1
转载请保持文档完全或保留出处(http://lenky.info/)
两类,启动 nginx 的主进程充当监控进程,而由主进程 fork 出来的子进程则充当工作进程。
工作进程的任务自然是完成具体的业务逻辑,而监控进程充当整个进程组的对外接口,同
时对工作进程进行监护,比如如果某工作进程意外退出,监控进程将重新 fork 生成一个新
的工作进程。nginx 也可以单进程模型执行,在这种进程模型下,主进程就是工作进程,此
时没有监控进程,单进程模型比较简单且官方建议仅供测试使用,所以下面主要分析多进
程模型。
分析 nginx 多进程模型的入口函数为主进程的 ngx_master_process_cycle()函数,在该函
数做完信号处理设置等之后就会调用一个名为 ngx_start_worker_processes()的函数用于 fork
产生出子进程(子进程数目通过函数调用的第二个实参指定),子进程作为一个新的实体
开始充当工作进程的角色执行 ngx_worker_process_cycle()函数,该函数主体为一个无限 for
循环,持续不断的处理客户端的服务请求,而主进程继续执行 ngx_master_process_cycle()
函数,也就是作为监控进程执行主体 for 循环,这也是一个无限循环,直到进程终止才退
出,服务进程基本都是这种写法,所以不用详述,下面先看看这个模型的图示:
上图中表现得很明朗,监控进程和工作进程各有一个无限 for 循环,以便进程持续的
等待和处理自己负责的事务,直到进程退出。
监控进程的无限 for 循环内有一个关键的 sigsuspend()函数调用,该函数的调用使得监
控进程的大部分时间都处于挂起等待状态,直到监控进程接收到信号为止,当监控进程接
收到信号时,信号处理函数 ngx_signal_handler()就会被执行,我们知道信号处理函数一般
都要求足够简单(关于信号处理函数的实现准则请 Google),所以在该函数内执行的动作
主要也就是根据当前信号值对相应的旗标变量做设置,而实际的处理逻辑必须放在主体代
码里来处理,所以该 for 循环接下来的代码就是判断有哪些旗标变量被设置而需要处理的,
比如 ngx_reap(有子进程退出?)、ngx_quit 或 ngx_terminate(进行要退出或终止?注意:
虽然两个旗标都是表示结束 nginx,不过 ngx_quit 的结束更优雅,它会让 nginx 监控进程做
欢迎访问 Lenky 个人网站:http://lenky.info/
2
转载请保持文档完全或保留出处(http://lenky.info/)
一些清理工作且等待子进程也完全清理并退出之后才终止,而 ngx_terminate 更为粗暴,不
过它通过使用 SIGKILL 信号能保证在一段时间后必定被结束掉)、ngx_reconfigure(重新
加载配置?)等。当所有信号都处理完时又挂起在函数 sigsuspend()调用处继续等待新的信
号,如此反复,构成监控进程的主要执行体。
82: Filename : ngx_process_cycle.c
83: void
84: ngx_master_process_cycle(ngx_cycle_t *cycle)
85: {
86: …
146: for ( ;; ) {
147: …
170: sigsuspend(&set);
171: …
177: if (ngx_reap) {
178: …
184: if (!live && (ngx_terminate || ngx_quit)) {
185: …
188: if (ngx_terminate) {
189: …
210: if (ngx_quit) {
211: …
212: }
213: …
工作进程的执行主体与监控进程类似,不过工作进程既名之为工作进程,那么它的主
要关注点就是与客户端或后端真实服务器(此时 nginx 作为中间代理)之间的数据可读/可
写等交互事件,而不是进程信号,所以工作进程的阻塞点是在像 select()、epoll_wait()等这
样的 I/O 多路复用函数调用处,以等待发生数据可读/可写事件,当然,也可能被新收到的
进程信号中断。关于 I/O 多路复用的更多细节,请参考其他章节。
721: Filename : ngx_process_cycle.c
722: static void
723: ngx_worker_process_cycle(ngx_cycle_t *cycle, void *data)
724: {
725: …
780: for ( ;; ) {
781:
782: if (ngx_exiting) {
783: …
806: ngx_process_events_and_timers(cycle);
807:
808: if (ngx_terminate) {
809: …
810: }
811: …
欢迎访问 Lenky 个人网站:http://lenky.info/
3
转载请保持文档完全或保留出处(http://lenky.info/)
整体架构
如 前 面 介 绍 的 那 样 , 正 常 执 行 起 来 后 的 Nginx 会 有 多 个 进 程 , 最 基 本 的 有
master_process 和 worker_process,还可能会有 cache 相关进程(这在后面会具体讲到)。
除了自身进程之间的相互通信,Nginx 还凭借强悍的模块功能与外界四通八达,比如通过
upstream 与 web server 通信、依靠 fastcgi 与 application server 通信等等。一个较为完整的整
体架构框图如下所示:
进程通信
运行在多进程模型的 nginx 在正常工作时,自然就会有多个进程实例,比如下图是在
配置“worker_processes 4;”情况下的显示,nginx 设置的进程 title 能很好的帮助我们区分监
控进程与工作进程,不过带上选项 f 的 ps 命令以树目录的形式打印各个进程信息也能帮助
我们做这个区分。多进程联合工作必定要牵扯到进程之间的通信问题,下面就来看看 nginx
是如何做的。
采用 socketpair()函数创造一对未命名的 UNIX 域套接字来进行 Linux 下具有亲缘关系
的进程之间的双向通信是一个非常不错的解决方案。nginx 就是这么做的,先看 fork 生成新
工作进程的 ngx_spawn_process()函数以及相关代码:
21: Filename : ngx_process.h
欢迎访问 Lenky 个人网站:http://lenky.info/
4
转载请保持文档完全或保留出处(http://lenky.info/)
22: typedef struct {
23: ngx_pid_t pid;
24: int status;
25: ngx_socket_t channel[2];
26: …
27: } ngx_process_t;
28: …
47: #define NGX_MAX_PROCESSES 1024
35: Filename : ngx_process.c
36: ngx_process_t ngx_processes[NGX_MAX_PROCESSES];
37:
86: ngx_pid_t
87: ngx_spawn_process(ngx_cycle_t *cycle, ngx_spawn_proc_pt proc, void *data,
88: char *name, ngx_int_t respawn)
89: {
90: …
117: if (socketpair(AF_UNIX, SOCK_STREAM, 0, ngx_processes[s].channel) == -1)
118: …
186: pid = fork();
187: …
在该函数进行 fork()之前,先调用了 socketpair()创建一对 socket 描述符存放在变量
ngx_processes[s].channel 内(其中 s 标志在 ngx_processes 数组内第一个可用元素的下标,比
如最开始产生第一个工作进程时,可用元素的下标 s 为 0),而在 fork()之后,由于子进程
继承了父进程的资源,那么父子进程就都有了这一对 socket 描述符,而 nginx 将 channel[0]
给父进程使用,channel[1]给子进程使用,这样分别错开的使用不同 socket 描述符,即可实
现父子进程之间的双向通信:
除此之外,对于各个子进程之间,也可以进行双向通信。如前面所述,父子进程的通
信 channel 设定是自然而然的事情,而子进程之间的通信 channel 设定就涉及到进程之间文
件描述符(socket 描述符也属于文件描述符)的传递,因为虽然后生成的子进程通过继承
的 channel[0]能够往前生成的子进程发送信息,但前生成的子进程无法获知后生成子进程的
channel[0] 而 不能 发 送 信 息 , 所 以 后 生 成 的 子 进 程 必 须 利 用 已 知 的 前 生 成 子 进 程 的
channel[0]进行主动告知,下面来看看这个具体是怎样的。
在子进程的启动初始化函数 ngx_worker_process_init()里,会把 ngx_channel(也就是
channel[1])加入到读事件监听集里,对应的回调处理函数为 ngx_channel_handler():
834: Filename : ngx_process_cycle.c
835: static void
836: ngx_worker_process_init(ngx_cycle_t *cycle, ngx_uint_t priority)
837: {
838: …
994: if (ngx_add_channel_event(cycle, ngx_channel, NGX_READ_EVENT,
995: ngx_channel_handler)
欢迎访问 Lenky 个人网站:http://lenky.info/
5
剩余63页未读,继续阅读
资源评论
- 旮旯旮2016-02-24挺详细的资料
- ibenxing2016-02-17挺详细的资料
chenfish999
- 粉丝: 3
- 资源: 10
上传资源 快速赚钱
- 我的内容管理 展开
- 我的资源 快来上传第一个资源
- 我的收益 登录查看自己的收益
- 我的积分 登录查看自己的积分
- 我的C币 登录后查看C币余额
- 我的收藏
- 我的下载
- 下载帮助
安全验证
文档复制为VIP权益,开通VIP直接复制
信息提交成功