nginx源代码分析

Nginx可以开启多个进程,每个进程拥有最大上限128个子线程以及一定的可用连接数。如果你希望使用线程可以在配置文件中设置worker_threads这个参数,但这个参数在Nginx官方手册上没有。只有通过阅读源代码才看到。最大客户端连接数等于进程数与连接数的乘积,连接是在主进程中初始化的,一开始所有连接处于空闲状态。

每一个客户端请求进来以后会通过事件处理机制,在Linux是Epoll,在FreeBSD下是KQueue放到空闲的连接里。

如果设置了线程数,那么被填充的连接会在子线程中处理,否则会在主线程中依次处理。

如果解析出是动态脚本请求,会根据fast-cgi的设置访问php-cgi进程,php进程数量的多少依据php-fpm.conf中max_children的设置。

因此Nginx的动态请求能力不仅仅依靠Nginx本身的设置,还要调试php-fpm。

从源代码级别上看nginx由以下几个元素组成:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
1. worker(进程)
2. thread(线程)
3. connection(连接)
4. event(事件)
5. module(模块)
6. pool(内存池)
7. cycle(全局设置)
8. log(日志)
 
大概就这些元素组成的。
 
整个程序从main()开始算
 
    ngx_max_module = 0;
    for (i = 0; ngx_modules[i]; i++) {
        ngx_modules[i]->index = ngx_max_module++;
    }
 
这几句比较关键,对加载的模块点一下数,看有多少个。ngx_modules并不是在原代码中被赋值的,你先执行一下./configure命令生成用于编译的make环境。在根目录会多出来一个文件夹objs,找到ngx_modules.c文件,默认情况下nginx会加载大约30个模块,的确不少,如果你不需要那个模块尽量还是去掉好一些。
 
接下来比较重要的函数是 ngx_init_cycle(),这个函数初始化系统的配置以及网络连接等,如果是多进程方式加载的会继续调用ngx_master_process_cycle(),这是main函数中调用的最关键的两个函数。
 
ngx_init_cycle()实际上是个复杂的初始化函数,首先是加载各子模块的配置信息、并初始化各组成模块。
 
任何模块都有两个重要接口组成,一个是create_conf,一个是init_conf。分别是创建配置和初始化配置信息。
 
模块按照先后顺序依次初始化,大概是这样的:
 
    &ngx_core_module,
    &ngx_errlog_module,
    &ngx_conf_module,
    &ngx_events_module,
    &ngx_event_core_module,
    &ngx_epoll_module,
    &ngx_http_module,
    &ngx_http_core_module,
    &ngx_http_log_module,
 
首先是内核模块、错误日志、配置模块、事件模块、时间内核模块、EPOLL模块、http模块、http内核模块、http日志模块,剩下的模块都算不上关键。
 
epoll是比较关键的核心模块之一,nginx兼容多种IO控制模型,memecached用的是libevent不如nginx彻头彻尾是自己实现的。
 
在ngx_init_cycle()中对模块初始化完毕后,调用ngx_open_listening_sockets()函数对socket进行了初始化。
 
在listen上80端口以后,调用模块的另外一个重要接口init_module对各模块进行初始化。
 
并不是每个模块都对init_module接口进行了定义,在比较重要的模块中仅有 ngx_http_log_module 对这个接口进行了定义。
 
ngx_init_cycle()返回后,主要的工作都是在ngx_master_process_cycle()函数中继续进行的。
 
 
ngx_master_process_cycle()函数中的重要过程有调用ngx_start_worker_processes()生成多个子进程,一般nginx是多进程的。
 
ngx_start_worker_processes()函数内部调用ngx_worker_process_cycle()函数建立每个进程的实际工 作内容,在这个函数中首先调用ngx_create_thread()初始化各线程。我们知道每个线程都有一个启动处理函数,nginx的线程处理函数为 ngx_worker_thread_cycle(),内部过程中最重要的是对ngx_event_thread_process_posted()函数 的调用,用于实际处理每一次请求。
 
在初始化线程结束后,首先调用ngx_process_events_and_timers()函数,该函数继续调用 ngx_process_events接口监听事件,一般情况下对应的函数是ngx_epoll_process_events(),如果使用的是其它种 类的IO模型,则应该实现相应的实际函数。这个接口负责把事件投递到ngx_posted_events事件队列里,并在 ngx_event_thread_process_posted()函数中进行处理。
 
nginx的connection和event是按照链表的方式进行存放的,区别在于connection是单向链表而event是双向链表。
 
nginx中的connection链表是提前分配的,定义在ngx_event_process_init()函数内,具体代码如下:
 
    ...
 
    cycle->connections = ngx_alloc(sizeof(ngx_connection_t) * ecf->connections,
                                   cycle->log);
 
    ...
 
    i = cycle->connection_n;
    next = NULL;
 
    do {
        i--;
 
        c[i].data = next;
        c[i].read = &cycle->read_events[i];
        c[i].write = &cycle->write_events[i];
        c[i].fd = (ngx_socket_t) -1;
 
        next = &c[i];
 
#if (NGX_THREADS)
        c[i].lock = 0;
#endif
    } while (i);
 
    cycle->free_connections = next;
    cycle->free_connection_n = ecf->connections;
 
在内存池里为所有connection分配空间,并依次初始化,并依次初始化各连接的链表关系,也就是在data上存下一个connection的指针。
 
在具体应用中通过ngx_get_connection()函数取出空闲的connection并使用。
 
至于event是一个双向链表,链表的入队和出队有下面的定义:
 
#define ngx_locked_post_event(ev, queue)                                      \
                                                                              \
    if (ev->prev == NULL) {                                                   \
        ev->next = (ngx_event_t *) *queue;                                    \
        ev->prev = (ngx_event_t **) queue;                                    \
        *queue = ev;                                                          \
                                                                              \
        if (ev->next) {                                                       \
            ev->next->prev = &ev->next;                                       \
        }                                                                     \
                                                                              \
        ngx_log_debug1(NGX_LOG_DEBUG_CORE, ev->log, 0, "post event %p", ev); \
                                                                              \
    } else {                                                                 \
        ngx_log_debug1(NGX_LOG_DEBUG_CORE, ev->log, 0,                        \
                       "update posted event %p", ev);                         \
    }
 
 
#define ngx_post_event(ev, queue)                                             \
                                                                              \
    ngx_mutex_lock(ngx_posted_events_mutex);                                  \
    ngx_locked_post_event(ev, queue);                                         \
    ngx_mutex_unlock(ngx_posted_events_mutex);
 
 
#define ngx_delete_posted_event(ev)                                           \
                                                                              \
    *(ev->prev) = ev->next;                                                   \
                                                                              \
    if (ev->next) {                                                           \
        ev->next->prev = ev->prev;                                            \
    }                                                                         \
                                                                              \
    ev->prev = NULL;                                                          \
    ngx_log_debug1(NGX_LOG_DEBUG_CORE, ev->log, 0,                            \
                   "delete posted event %p", ev);
 
简单说 ngx_post_event 用于插入事件、ngx_delete_posted_event 用于删除事件。这两个是宏定义,会比函数定义用起来节省。
 
整个程序就围绕这event和connection进行。不断的投入、拿出。
 
nginx的模块处理
 
nginx由若干模块组成,但所有模块全部采用静态编译的办法。我们以rewrite模块为例,
我们输入./configure --help 命令可以查看的有关rewrite模块的描述
 
--without-http_rewrite_module      disable ngx_http_rewrite_module
 
这是在auto/options下定义的,在auto/soruces里面指定了与这个模块有关的源代码位置
 
HTTP_REWRITE_MODULE=ngx_http_rewrite_module
HTTP_REWRITE_SRCS=src/http/modules/ngx_http_rewrite_module.c
 
在auto/summary下提供了关于rewrite模块的依赖条件检查:
 
if [ $HTTP_REWRITE = YES ]; then
    if [ $USE_PCRE = DISABLED ]; then
 
cat << END
$0: error: the HTTP rewrite module requires the PCRE library.
You can either disable the module by using --without-http_rewrite_module
option or you have to enable the PCRE support.
 
END
        exit 1
    fi
 
    if [ $PCRE = NONE -o $PCRE = NO ]; then
 
cat << END
$0: error: the HTTP rewrite module requires the PCRE library.
You can either disable the module by using --without-http_rewrite_module
option, or install the PCRE library into the system, or build the PCRE library
statically from the source with nginx by using --with-pcre=<path> option.
 
END
 
        exit 1
    fi
fi
 
下面是对rewrite模块代码分析,其它模块也带有同样的结构。
 
一般模块都会有一个ngx_command_t结构,存放在ngingx配置文件中,这个模块的一些语法命令,详细结构为:
 
struct ngx_command_s {
    ngx_str_t             name;
    ngx_uint_t            type;
    char               *(*set)(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
    ngx_uint_t            conf;
    ngx_uint_t            offset;
    void                 *post;
};
 
name 是名称
type 是类型
set 是处理这个命令的具体函数
conf 属于那种配置,一共三种 main结构、server结构、location结构。
offset 在配置文件中的偏移量。
post 没见到用到。
 
下面是和rewrite模块中的代码:
 
static ngx_command_t ngx_http_rewrite_commands[] = {
 
    { ngx_string("rewrite"),
      NGX_HTTP_SRV_CONF|NGX_HTTP_SIF_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF
                       |NGX_CONF_TAKE23,
      ngx_http_rewrite,
      NGX_HTTP_LOC_CONF_OFFSET,
      0,
      NULL },
 
    { ngx_string("return"),
      NGX_HTTP_SRV_CONF|NGX_HTTP_SIF_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF
                       |NGX_CONF_TAKE1,
      ngx_http_rewrite_return,
      NGX_HTTP_LOC_CONF_OFFSET,
      0,
      NULL },
 
    { ngx_string("break"),
      NGX_HTTP_SRV_CONF|NGX_HTTP_SIF_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF
                       |NGX_CONF_NOARGS,
      ngx_http_rewrite_break,
      NGX_HTTP_LOC_CONF_OFFSET,
      0,
      NULL },
 
    { ngx_string("if"),
      NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_BLOCK|NGX_CONF_1MORE,
      ngx_http_rewrite_if,
      NGX_HTTP_LOC_CONF_OFFSET,
      0,
      NULL },
 
    { ngx_string("set"),
      NGX_HTTP_SRV_CONF|NGX_HTTP_SIF_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF
                       |NGX_CONF_TAKE2,
      ngx_http_rewrite_set,
      NGX_HTTP_LOC_CONF_OFFSET,
      0,
      NULL },
 
    { ngx_string("rewrite_log"),
      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_SIF_CONF|NGX_HTTP_LOC_CONF
                        |NGX_HTTP_LIF_CONF|NGX_CONF_FLAG,
      ngx_conf_set_flag_slot,
      NGX_HTTP_LOC_CONF_OFFSET,
      offsetof(ngx_http_rewrite_loc_conf_t, log),
      NULL },
 
    { ngx_string("uninitialized_variable_warn"),
      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_SIF_CONF|NGX_HTTP_LOC_CONF
                        |NGX_HTTP_LIF_CONF|NGX_CONF_FLAG,
      ngx_conf_set_flag_slot,
      NGX_HTTP_LOC_CONF_OFFSET,
      offsetof(ngx_http_rewrite_loc_conf_t, uninitialized_variable_warn),
      NULL },
 
      ngx_null_command
};
 
rewrite模块内部一共有四类函数,关键的一类为一类是动作处理函数,一共有:
 
ngx_http_rewrite_init 模块初始化函数。
ngx_http_rewrite_handler 在具体请求中的处理函数。
 
这两个函数通过在模块初始化的过程中,把handler推送到phases结构中,这是一个简单数组,请求会对数组中存放的每一个handler依次进行处理。如果我们自己写什么模块也应该放进去,例如同样的ngx_http_access_init()等初始化函数也是把自己模块的ngx_http_access_handler注册进去。
 
nginx在内部已经定义了所有可以在http请求中被处理的模块列表:
 
typedef enum {
    NGX_HTTP_POST_READ_PHASE = 0,
 
    NGX_HTTP_SERVER_REWRITE_PHASE,
 
    NGX_HTTP_FIND_CONFIG_PHASE,
    NGX_HTTP_REWRITE_PHASE,
    NGX_HTTP_POST_REWRITE_PHASE,
 
    NGX_HTTP_PREACCESS_PHASE,
 
    NGX_HTTP_ACCESS_PHASE,
    NGX_HTTP_POST_ACCESS_PHASE,
 
    NGX_HTTP_CONTENT_PHASE,
 
    NGX_HTTP_LOG_PHASE
} ngx_http_phases;
 
和rewrite有关的一共是两个,都是在ngx_http_rewrite_handler()函数中进行处理
 
一类为与配置文件有关的创建和合并函数,一共有:
 
ngx_http_rewrite_create_loc_conf
ngx_http_rewrite_merge_loc_conf
 
 
一类作为common命令的解析函数,一共有:
 
ngx_http_rewrite
ngx_http_rewrite_return
ngx_http_rewrite_break
ngx_http_rewrite_if
ngx_http_rewrite_set
 
一类为解析命令中内部使用的函数,一共有:
 
ngx_http_rewrite_if_condition
ngx_http_rewrite_variable
ngx_http_rewrite_value
ngx_http_rewrite_var

langwan 2008.11
http://hi.baidu.com/langwan

发表评论

电子邮件地址不会被公开。 必填项已用*标注