# Nginx 主模块原理 二
ngx_event_core_module 模块
该模块核心功能非常简单:创建 nginx.conf (opens new window) events 块中的配置信息,同时赋值给 事件循环 结构,然后根据配置信息检测系统是否支持所配置的多路复用器。同时该结构存在 init module 和 init process 回调,在回调中将会调用事件处理模块的对应回调函数。同时每个连接都将包装在 ngx_connection_t 结构中,事件结构 ngx_event_t 将用于表示注册到 多路复用器的事件类型信息。源码如下。
// 注意这里存在:init module(初始化事件循环结构时,此时子进程未启动,调用) 和 init process(当子进程创建成功后回调) 回调
ngx_module_t ngx_event_core_module = {
NGX_MODULE_V1,
&ngx_event_core_module_ctx, /* module context */
ngx_event_core_commands, /* module directives */
NGX_EVENT_MODULE, /* module type */
NULL, /* init master */
ngx_event_module_init, /* init module */
ngx_event_process_init, /* init process */
NULL, /* init thread */
NULL, /* exit thread */
NULL, /* exit process */
NULL, /* exit master */
NGX_MODULE_V1_PADDING
};
// 拥有创建和初始化 conf 的回调函数
static ngx_event_module_t ngx_event_core_module_ctx = {
&event_core_name,
ngx_event_core_create_conf, /* create configuration */
ngx_event_core_init_conf, /* init configuration */
{ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }
};
// 忽略掉其他配置,默认 nginx.conf 只有该配置项
static ngx_command_t ngx_event_core_commands[] = {
{ ngx_string("worker_connections"), // 解析到 events 模块的该字符串时回调
NGX_EVENT_CONF|NGX_CONF_TAKE1,
ngx_event_connections,
0,
0,
NULL },
};
// 创建配置结构,可以看到这里同样先分配空间,然后初始化为初始值(通常为无效值 -1)
static void * ngx_event_core_create_conf(ngx_cycle_t *cycle)
{
ngx_event_conf_t *ecf;
ecf = ngx_palloc(cycle->pool, sizeof(ngx_event_conf_t));
if (ecf == NULL) {
return NULL;
}
ecf->connections = NGX_CONF_UNSET_UINT;
ecf->use = NGX_CONF_UNSET_UINT;
ecf->multi_accept = NGX_CONF_UNSET;
ecf->accept_mutex = NGX_CONF_UNSET;
ecf->accept_mutex_delay = NGX_CONF_UNSET_MSEC;
ecf->name = (void *) NGX_CONF_UNSET;
return ecf;
}
// 初始化该模块的配置项为默认值
static char * ngx_event_core_init_conf(ngx_cycle_t *cycle, void *conf)
{
ngx_event_conf_t *ecf = conf;
#if (NGX_HAVE_EPOLL) && !(NGX_TEST_BUILD_EPOLL) // 配置使用了epoll,那么保存epoll fd
int fd;
#endif
ngx_int_t i;
ngx_module_t *module;
ngx_event_module_t *event_module;
module = NULL;
#if (NGX_HAVE_EPOLL) && !(NGX_TEST_BUILD_EPOLL) // 创建 epoll fd(注意:该 fd 不使用哈,仅仅用于探测OS是否支持该系统调用,也就看看错误号或者返回值)
fd = epoll_create(100);
if (fd != -1) { // 正常分配,那么仅作为测试用,所以释放掉,并选取 epoll 模块
(void) close(fd);
module = &ngx_epoll_module;
} else if (ngx_errno != NGX_ENOSYS) { // 非 NGX_ENOSYS 错误号,表明OS 支持该特性,那么获取 epoll 模块地址
module = &ngx_epoll_module;
}
#endif
// 宏定义判断是否为其他多路复用器,我们这里关注 epoll 即可
#if (NGX_HAVE_DEVPOLL) && !(NGX_TEST_BUILD_DEVPOLL)
module = &ngx_devpoll_module;
#endif
#if (NGX_HAVE_KQUEUE)
module = &ngx_kqueue_module;
#endif
#if (NGX_HAVE_SELECT)
if (module == NULL) {
module = &ngx_select_module;
}
#endif
if (module == NULL) { // 若未配置 module,那么遍历当前配置的所有模块,找到可以使用的事件循环模块
for (i = 0; cycle->modules[i]; i++) {
if (cycle->modules[i]->type != NGX_EVENT_MODULE) {
continue;
}
event_module = cycle->modules[i]->ctx;
if (ngx_strcmp(event_module->name->data, event_core_name.data) == 0)
{
continue;
}
module = cycle->modules[i];
break;
}
}
if (module == NULL) { // 仍未找到,直接报错,因为不能没有处理IO的事件模块
ngx_log_error(NGX_LOG_EMERG, cycle->log, 0, "no events module found");
return NGX_CONF_ERROR;
}
// 初始化变量为对应默认值(#define DEFAULT_CONNECTIONS 512)
ngx_conf_init_uint_value(ecf->connections, DEFAULT_CONNECTIONS);
cycle->connection_n = ecf->connections;
ngx_conf_init_uint_value(ecf->use, module->ctx_index);
event_module = module->ctx;
ngx_conf_init_ptr_value(ecf->name, event_module->name->data);
ngx_conf_init_value(ecf->multi_accept, 0);
ngx_conf_init_value(ecf->accept_mutex, 0);
ngx_conf_init_msec_value(ecf->accept_mutex_delay, 500);
return NGX_CONF_OK;
}
// 解析到 worker_connections 字符串时回调,我们看到直接读取该配置保存到 cf->cycle->connection_n 中
static char * ngx_event_connections(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
ngx_event_conf_t *ecf = conf;
ngx_str_t *value;
if (ecf->connections != NGX_CONF_UNSET_UINT) {
return "is duplicate";
}
value = cf->args->elts;
ecf->connections = ngx_atoi(value[1].data, value[1].len);
if (ecf->connections == (ngx_uint_t) NGX_ERROR) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"invalid number \"%V\"", &value[1]);
return NGX_CONF_ERROR;
}
cf->cycle->connection_n = ecf->connections;
return NGX_CONF_OK;
}
// 模块初始化回调(获取涉及到的所有模块的配置并根据配置创建对应结构)
static ngx_int_t ngx_event_module_init(ngx_cycle_t *cycle)
{
... // 省略掉变量定义
cf = ngx_get_conf(cycle->conf_ctx, ngx_events_module); // 获取事件处理模块的配置,这里对应 epoll 的事件模块
ecf = (*cf)[ngx_event_core_module.ctx_index]; // 获取自身模块的配置信息
if (!ngx_test_config && ngx_process <= NGX_PROCESS_MASTER) {
ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0,
"using the \"%s\" event method", ecf->name);
}
ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module); // 获取核心模块的配置信息
... // 省略掉:设置文件限制、设置accept锁多进程使用的共享内存等等,这些我们先暂时忽略,关注点在于:epoll事件模块的处理
return NGX_OK;
}
// 进程执行时初始化回调
static ngx_int_t ngx_event_process_init(ngx_cycle_t *cycle)
{
ngx_uint_t m, i;
ngx_event_t *rev, *wev;
ngx_listening_t *ls;
ngx_connection_t *c, *next, *old;
ngx_core_conf_t *ccf;
ngx_event_conf_t *ecf;
ngx_event_module_t *module;
// 获取核心模块、当前模块的配置信息,然后根据配置决定是否使用互斥锁(存在master、存在多个worker、指定使用互斥锁)
ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module);
ecf = ngx_event_get_conf(cycle->conf_ctx, ngx_event_core_module);
if (ccf->master && ccf->worker_processes > 1 && ecf->accept_mutex) {
ngx_use_accept_mutex = 1;
ngx_accept_mutex_held = 0;
ngx_accept_mutex_delay = ecf->accept_mutex_delay;
} else {
ngx_use_accept_mutex = 0;
}
ngx_use_exclusive_accept = 0;
// 初始化事件队列(我们在整体启动流程中看到过,事件将在这些队列中流转)
ngx_queue_init(&ngx_posted_accept_events);
ngx_queue_init(&ngx_posted_next_events);
ngx_queue_init(&ngx_posted_events);
// 初始化管理时间事件的 红黑树 结构
if (ngx_event_timer_init(cycle->log) == NGX_ERROR) {
return NGX_ERROR;
}
// 遍历所有配置的事件模块找到目前使用的模块,回调其 init 函数(这里也即回调 ngx_epoll_module 的init函数)
for (m = 0; cycle->modules[m]; m++) {
if (cycle->modules[m]->type != NGX_EVENT_MODULE) {
continue;
}
if (cycle->modules[m]->ctx_index != ecf->use) {
continue;
}
module = cycle->modules[m]->ctx;
if (module->actions.init(cycle, ngx_timer_resolution) != NGX_OK) {
exit(2);
}
break;
}
...
cycle->connections =
ngx_alloc(sizeof(ngx_connection_t) * cycle->connection_n, cycle->log); // 分配连接结构数组
if (cycle->connections == NULL) {
return NGX_ERROR;
}
c = cycle->connections;
cycle->read_events = ngx_alloc(sizeof(ngx_event_t) * cycle->connection_n,
cycle->log); // 分配保存读事件的数组
if (cycle->read_events == NULL) {
return NGX_ERROR;
}
rev = cycle->read_events;
for (i = 0; i < cycle->connection_n; i++) { // 初始化读事件结构默认值
rev[i].closed = 1;
rev[i].instance = 1;
}
cycle->write_events = ngx_alloc(sizeof(ngx_event_t) * cycle->connection_n,
cycle->log); // 分配保存写事件的数组
if (cycle->write_events == NULL) {
return NGX_ERROR;
}
wev = cycle->write_events;
for (i = 0; i < cycle->connection_n; i++) { // 初始化写事件结构默认值
wev[i].closed = 1;
}
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; // 对应客户端 fd 初始化为 -1
next = &c[i];
} while (i);
// 保存当前空闲的连接结构和数量
cycle->free_connections = next;
cycle->free_connection_n = cycle->connection_n;
ls = cycle->listening.elts;
for (i = 0; i < cycle->listening.nelts; i++) { // 遍历之前我们看到如果使用 REUSEPORT 时,复制的 server fd
#if (NGX_HAVE_REUSEPORT)
if (ls[i].reuseport && ls[i].worker != ngx_worker) {
continue;
}
#endif
// 获取一个空闲的连接结构绑定到当前 server fd,并设置对应信息
c = ngx_get_connection(ls[i].fd, cycle->log);
if (c == NULL) {
return NGX_ERROR;
}
c->type = ls[i].type;
c->log = &ls[i].log;
c->listening = &ls[i];
ls[i].connection = c;
rev = c->read;
rev->log = c->log;
rev->accept = 1; // 当前为接收连接事件
...
rev->handler = (c->type == SOCK_STREAM) ? ngx_event_accept
: ngx_event_recvmsg; // 事件处理器为 ngx_event_accept(为何不是 ngx_event_recvmsg?因为我们通常使用 TCP)
#if (NGX_HAVE_REUSEPORT) // 配置使用端口复用,那么回调 EPOLL 模块的 add 回调函数,添加读事件
if (ls[i].reuseport) {
if (ngx_add_event(rev, NGX_READ_EVENT, 0) == NGX_ERROR) {
return NGX_ERROR;
}
continue;
}
#endif
if (ngx_use_accept_mutex) { // 若使用互斥锁,那么继续循环
continue;
}
#if (NGX_HAVE_EPOLLEXCLUSIVE) // 否则使用内核 4.x 的新特性:内核自己维护多进程的 EPOLL 唤醒(也即当多个进程使用 epoll fd 监听了同一个 server fd时,连接到了,只会通知一个进程来处理)
if ((ngx_event_flags & NGX_USE_EPOLL_EVENT)
&& ccf->worker_processes > 1)
{
ngx_use_exclusive_accept = 1; // 标识 使用 epoll 互斥
if (ngx_add_event(rev, NGX_READ_EVENT, NGX_EXCLUSIVE_EVENT) // 添加读事件
== NGX_ERROR)
{
return NGX_ERROR;
}
continue;
}
#endif
if (ngx_add_event(rev, NGX_READ_EVENT, 0) == NGX_ERROR) {
return NGX_ERROR;
}
#endif
}
return NGX_OK;
}
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
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
# ngx_epoll_module 模块
该模块使用了 Epoll 作为多路复用器实现。通过代码我们不难得到一个结论:每个worker 进程创建了自己的事件循环以及epoll 多路复用器。
ngx_module_t ngx_epoll_module = {
NGX_MODULE_V1,
&ngx_epoll_module_ctx, /* module context */
ngx_epoll_commands, /* module directives */
NGX_EVENT_MODULE, /* module type */
NULL, /* init master */
NULL, /* init module */
NULL, /* init process */
NULL, /* init thread */
NULL, /* exit thread */
NULL, /* exit process */
NULL, /* exit master */
NGX_MODULE_V1_PADDING
};
// 包含创建配置与初始化配置回调
static ngx_event_module_t ngx_epoll_module_ctx = {
&epoll_name,
ngx_epoll_create_conf, /* create configuration */
ngx_epoll_init_conf, /* init configuration */
// 定义了事件模块中的:ngx_event_actions_t actions 结构,这些回调方法我们之前看到过:ngx_epoll_process_events,当需要操作事件时回调(英文自己看吧,我就不翻译了,因为很简单)
{
ngx_epoll_add_event, /* add an event */
ngx_epoll_del_event, /* delete an event */
ngx_epoll_add_event, /* enable an event */
ngx_epoll_del_event, /* disable an event */
ngx_epoll_add_connection, /* add an connection */
ngx_epoll_del_connection, /* delete an connection */
#if (NGX_HAVE_EVENTFD) // 存在Linux 的 eventfd ,那么使用该fd进行通知 epoll(咋弄?把一个fd注册到epoll中,如果要通知,那么直接写数据就行)
ngx_epoll_notify, /* trigger a notify */
#else
NULL, /* trigger a notify */
#endif
ngx_epoll_process_events, /* process the events */
ngx_epoll_init, /* init the events */
ngx_epoll_done, /* done the events */
}
};
// 解析到 对应字符串时,回调,我们直接不看了- - ,因为这玩意儿直接跟前面一样:读取配置的信息赋值,,
static ngx_command_t ngx_epoll_commands[] = {
{ ngx_string("epoll_events"),
NGX_EVENT_CONF|NGX_CONF_TAKE1,
ngx_conf_set_num_slot,
0,
offsetof(ngx_epoll_conf_t, events),
NULL },
{ ngx_string("worker_aio_requests"),
NGX_EVENT_CONF|NGX_CONF_TAKE1,
ngx_conf_set_num_slot,
0,
offsetof(ngx_epoll_conf_t, aio_requests),
NULL },
ngx_null_command
};
// epoll 配置只有两个,所以很简单
static void * ngx_epoll_create_conf(ngx_cycle_t *cycle)
{
ngx_epoll_conf_t *epcf;
epcf = ngx_palloc(cycle->pool, sizeof(ngx_epoll_conf_t));
if (epcf == NULL) {
return NULL;
}
epcf->events = NGX_CONF_UNSET;
epcf->aio_requests = NGX_CONF_UNSET;
return epcf;
}
// 初始化为默认值
static char * ngx_epoll_init_conf(ngx_cycle_t *cycle, void *conf)
{
ngx_epoll_conf_t *epcf = conf;
ngx_conf_init_uint_value(epcf->events, 512);
ngx_conf_init_uint_value(epcf->aio_requests, 32);
return NGX_CONF_OK;
}
// 在 ngx_event_core_module 中我们看到:将在其 ngx_event_process_init 回调函数中 回调该方法(注意哈:调用该方法的均为 worker 进程,因为 process_init 的生命周期 将在子进程 fork 成功后 由每个子进程执行),那么这就可以得到结论:每个 worker 进程 均持有自己的 epoll fd
static ngx_int_t ngx_epoll_init(ngx_cycle_t *cycle, ngx_msec_t timer){
ngx_epoll_conf_t *epcf;
epcf = ngx_event_get_conf(cycle->conf_ctx, ngx_epoll_module); // 获取当前模块配置信息
if (ep == -1) { // 创建 epoll fd
ep = epoll_create(cycle->connection_n / 2);
if (ep == -1) {
ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
"epoll_create() failed");
return NGX_ERROR;
}
#if (NGX_HAVE_EVENTFD) // 调用系统调用eventfd 创建 notify_fd ,并将其通过 epoll_ctl 添加到 epoll 中监听,之后我们就可以通过向该 fd 写入数据唤醒 epoll,自己打开看,很简单~
if (ngx_epoll_notify_init(cycle->log) != NGX_OK) {
ngx_epoll_module_ctx.actions.notify = NULL;
}
#endif
#if (NGX_HAVE_FILE_AIO) // 初始化 libaio 的创建,这个后面会单独一篇文章来描述 linux aio 的实现原理~别着急了解下即可
ngx_epoll_aio_init(cycle, epcf);
#endif
#if (NGX_HAVE_EPOLLRDHUP) // 配置了 epoll 检测 对端断开连接(EPOLLRDHUP标志位检测) 事件,那么执行检测。在内部使用 socketpair(AF_UNIX, SOCK_STREAM, 0, s) 创建 一对 unix socket 的 fd,把其中一个fd s[0] 注册到 epoll中,随后关闭 s[1] 看看是否epoll 支持 EPOLLRDHUP标志位。其实就是做一下测试,看看内核中的 epoll 有没有这个标志位而已
ngx_epoll_test_rdhup(cycle);
#endif
}
if (nevents < epcf->events) { // 创建保存 epoll_event 的列表 event_list
if (event_list) {
ngx_free(event_list);
}
event_list = ngx_alloc(sizeof(struct epoll_event) * epcf->events,
cycle->log);
if (event_list == NULL) {
return NGX_ERROR;
}
}
nevents = epcf->events;
ngx_io = ngx_os_io;
ngx_event_actions = ngx_epoll_module_ctx.actions; // 将epoll_module 的回调函数保存到每个worker进程的全局变量 ngx_event_actions 中,此时将可以根据之前介绍的宏,直接调用这里注册的每个 epoll 关联函数
#if (NGX_HAVE_CLEAR_EVENT) // 根据配置项 在 ngx_event_flags 标志位中设置对应位,表示 EPOLL 使用 ET 边缘模式 (NGX_USE_CLEAR_EVENT)或者 LT 水平触发模式(NGX_USE_LEVEL_EVENT)来监听 对应的 FD,我们在下一篇文章中详细分析
ngx_event_flags = NGX_USE_CLEAR_EVENT
#else
ngx_event_flags = NGX_USE_LEVEL_EVENT
#endif
|NGX_USE_GREEDY_EVENT
|NGX_USE_EPOLL_EVENT;
return NGX_OK;
}
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
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