# Nginx 整体启动流程一
本文将详细解释 Nginx 整体启动流程,通过流程我们需要了解以下问题:
- 进程模型
- IO模型
在了解上述问题后,我们将根据之前介绍的 Linux socket 的选项原理,结合内核源码来看看 Nginx 如何高效处理高并发。总所周知,Nginx 使用 C语言来编写,而C语言与底层 ISA 指令集平台强相关,且在不同平台上表现得数据范围不同(32位 或者 64位),所以C类框架都会自定义自己的 数据结构 ,在 Nginx 中使用:ngx_xxx_t 的格式来定义,这些结构,我们在关键代码分析时再展开。
# main 函数
通过源码我们看到,nginx 将会根据传入参数来执行不同分支流程,同时 核心方法 在于:
- 初始事件循环结构,该结构仅作为 后面创建 真正的事件循环结构提供数据支持
- ngx_single_process_cycle 与 ngx_master_process_cycle 函数将会启动并执行整个事件循环
至于其他的方法,请读者根据注释来理解吧,涉及到的知识盲区将会在混沌学堂中讲解,不过那些盲区 并不影响主要流程分析,所以忽略即可。
int ngx_cdecl main(int argc, char *const *argv){
ngx_cycle_t *cycle, init_cycle; // 定义 master 进程 和 slave 进程的循环结构(ngx_cycle_t 表示事件循环,看过Netty源码的道友,这个应该很简单)
...
if (ngx_get_options(argc, argv) != NGX_OK) { // 根据命令行参数初始化全局变量
return 1;
}
...
ngx_time_init(); // 初始化时间模块
...
log = ngx_log_init(ngx_prefix, ngx_error_log); // 初始化日志模块
...
ngx_memzero(&init_cycle, sizeof(ngx_cycle_t)); // 将初始事件循环内部结构初始化为0
init_cycle.log = log; // 将日志对象与事件循环绑定
ngx_cycle = &init_cycle;// 初始循环将作为全局的事件循环
init_cycle.pool = ngx_create_pool(1024, log); // 创建初始循环的数据池
if (ngx_save_argv(&init_cycle, argc, argv) != NGX_OK) { // 将参数与初始循环绑定
return 1;
}
if (ngx_process_options(&init_cycle) != NGX_OK) { // 根据配置信息初始化初始循环的成员变量
return 1;
}
if (ngx_os_init(log) != NGX_OK) { // 初始化 OS 模块,保存 OS 的相关信息(CPU个数、fd限制、页大小、缓存行大小、初始化随机数等等)
return 1;
}
if (ngx_crc32_table_init() != NGX_OK) { // 初始化 CRC32 模块
return 1;
}
ngx_slab_sizes_init(); // 计算保存 slab 分配内存大小
if (ngx_add_inherited_sockets(&init_cycle) != NGX_OK) { // 设置可以被子进程继承处理的 socket 集合(我们这里不指定该集合,所以该函数相当于空函数)
return 1;
}
if (ngx_preinit_modules() != NGX_OK) { // 预初始化模块(包含 core、http 等模块信息),在该函数内对 ngx_module_s 模块结构的 index 和 name 进行赋值。什么是模块?请看看 conf 配置文件就知道了
return 1;
}
cycle = ngx_init_cycle(&init_cycle); // 初始化事件循环
if (ngx_test_config) { // -t 参数启动,检测配置,不启动nginx
...
}
if (ngx_signal) { // -s 参数启动,表示给nginx 发送信号,不启动nginx
return ngx_signal_process(cycle, ngx_signal);
}
ngx_os_status(cycle->log); // 输出os状态
ngx_cycle = cycle; // 根据init_cycle 参数 创建的 新的 事件循环赋值给全局循环
ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module); // #define ngx_get_conf(conf_ctx, module) conf_ctx[module.index] 通过宏定义很明显看到:将事件循环配置上下文对应模块下标处的 核心模块 配置信息获取
if (ccf->master && ngx_process == NGX_PROCESS_SINGLE) { // 当前设置为 master 进程同时运行在单节点上(也即不启动worker)
ngx_process = NGX_PROCESS_MASTER;
}
if (ngx_init_signals(cycle->log) != NGX_OK) { // 初始化 nginx 自身的信号处理函数(默认的信号处理函数大部分直接杀死进程,什么是信号?这对于分析Nginx 的事件循环来说 并不重要,后面 并发处理专题中 混沌学堂会详细解释~)
return 1;
}
// 设置以 后台进程方式启动,那么很明显肯定在 ngx_daemon 函数中 fork,然后调用 setsid 脱离当前会话组 ,但,不需要研究,也不需要看,因为不管是否指定后台运行,父进程或者子进程都会继续执行剩下的代码,所以 是不是不重要(指定后台运行,那么子进程肯定继续执行,父进程 执行 exit 退出)
if (!ngx_inherited && ccf->daemon) {
if (ngx_daemon(cycle->log) != NGX_OK) {
return 1;
}
ngx_daemonized = 1;
}
if (ngx_inherited) {
ngx_daemonized = 1;
}
// 创建进程 PID 文件
if (ngx_create_pidfile(&ccf->pid, cycle->log) != NGX_OK) {
return 1;
}
// 重定向错误日志流(根据配置,将标准输出变为 log 输出到文件)
if (ngx_log_redirect_stderr(cycle) != NGX_OK) {
return 1;
}
...
// 根据当前运行状态启动:1、单进程服务 2、master - slave 多进程服务
if (ngx_process == NGX_PROCESS_SINGLE) {
ngx_single_process_cycle(cycle);
} else {
ngx_master_process_cycle(cycle);
}
return 0;
}
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
# ngx_preinit_modules 函数
该函数用于初始化模块索引和名字,同时计算最大模块索引信息。读者注意:ngx_modules 和 ngx_module_names 数组并没有在源码中给出,而是在编译时执行 configure 生成的,具体信息看下面的描述。
extern ngx_module_t *ngx_modules[];
extern char *ngx_module_names[];
extern ngx_uint_t ngx_max_module;
ngx_int_t ngx_preinit_modules(void)
{
ngx_uint_t i;
for (i = 0; ngx_modules[i]; i++) { // 遍历模块信息并赋值索引和名字
ngx_modules[i]->index = i;
ngx_modules[i]->name = ngx_module_names[i];
}
ngx_modules_n = i;
ngx_max_module = ngx_modules_n + NGX_MAX_DYNAMIC_MODULES;
return NGX_OK;
}
// 自动生成的 ngx_modules.c 源文件
ngx_module_t *ngx_modules[] = {
&ngx_core_module,
&ngx_errlog_module,
&ngx_conf_module,
&ngx_regex_module,
&ngx_events_module,
&ngx_event_core_module,
&ngx_epoll_module,
&ngx_http_module,
&ngx_http_core_module,
&ngx_http_log_module,
&ngx_http_upstream_module,
&ngx_http_static_module,
&ngx_http_autoindex_module,
&ngx_http_index_module,
&ngx_http_mirror_module,
&ngx_http_try_files_module,
&ngx_http_auth_basic_module,
&ngx_http_access_module,
&ngx_http_limit_conn_module,
&ngx_http_limit_req_module,
&ngx_http_geo_module,
&ngx_http_map_module,
&ngx_http_split_clients_module,
&ngx_http_referer_module,
&ngx_http_rewrite_module,
&ngx_http_proxy_module,
&ngx_http_fastcgi_module,
&ngx_http_uwsgi_module,
&ngx_http_scgi_module,
&ngx_http_memcached_module,
&ngx_http_empty_gif_module,
&ngx_http_browser_module,
&ngx_http_upstream_hash_module,
&ngx_http_upstream_ip_hash_module,
&ngx_http_upstream_least_conn_module,
&ngx_http_upstream_random_module,
&ngx_http_upstream_keepalive_module,
&ngx_http_upstream_zone_module,
&ngx_http_write_filter_module,
&ngx_http_header_filter_module,
&ngx_http_chunked_filter_module,
&ngx_http_range_header_filter_module,
&ngx_http_gzip_filter_module,
&ngx_http_postpone_filter_module,
&ngx_http_ssi_filter_module,
&ngx_http_charset_filter_module,
&ngx_http_userid_filter_module,
&ngx_http_headers_filter_module,
&ngx_http_copy_filter_module,
&ngx_http_range_body_filter_module,
&ngx_http_not_modified_filter_module,
NULL
};
char *ngx_module_names[] = {
"ngx_core_module",
"ngx_errlog_module",
"ngx_conf_module",
"ngx_regex_module",
"ngx_events_module",
"ngx_event_core_module",
"ngx_epoll_module",
"ngx_http_module",
"ngx_http_core_module",
"ngx_http_log_module",
"ngx_http_upstream_module",
"ngx_http_static_module",
"ngx_http_autoindex_module",
"ngx_http_index_module",
"ngx_http_mirror_module",
"ngx_http_try_files_module",
"ngx_http_auth_basic_module",
"ngx_http_access_module",
"ngx_http_limit_conn_module",
"ngx_http_limit_req_module",
"ngx_http_geo_module",
"ngx_http_map_module",
"ngx_http_split_clients_module",
"ngx_http_referer_module",
"ngx_http_rewrite_module",
"ngx_http_proxy_module",
"ngx_http_fastcgi_module",
"ngx_http_uwsgi_module",
"ngx_http_scgi_module",
"ngx_http_memcached_module",
"ngx_http_empty_gif_module",
"ngx_http_browser_module",
"ngx_http_upstream_hash_module",
"ngx_http_upstream_ip_hash_module",
"ngx_http_upstream_least_conn_module",
"ngx_http_upstream_random_module",
"ngx_http_upstream_keepalive_module",
"ngx_http_upstream_zone_module",
"ngx_http_write_filter_module",
"ngx_http_header_filter_module",
"ngx_http_chunked_filter_module",
"ngx_http_range_header_filter_module",
"ngx_http_gzip_filter_module",
"ngx_http_postpone_filter_module",
"ngx_http_ssi_filter_module",
"ngx_http_charset_filter_module",
"ngx_http_userid_filter_module",
"ngx_http_headers_filter_module",
"ngx_http_copy_filter_module",
"ngx_http_range_body_filter_module",
"ngx_http_not_modified_filter_module",
NULL
};
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
# ngx_init_cycle 函数
该函数相当之长,笔者这里只保留核心流程。入参 ngx_cycle_t *old_cycle 为main方法中根据配置信息初始化了成员变量的 事件循环,这里需要根据其信息创建真正的事件循环并返回。读者注意:此方法的核心为 ngx_open_listening_sockets 函数,该函数中将会对模块中添加的socket 进行创建,同时设置 socket的相关属性信息(setSocketOption)其中就包括:REUSEPORT 参数的设置。
ngx_cycle_t * ngx_init_cycle(ngx_cycle_t *old_cycle){
pool = ngx_create_pool(NGX_CYCLE_POOL_SIZE, log); // 创建数据池(也即内存池)
cycle = ngx_pcalloc(pool, sizeof(ngx_cycle_t)); // 分配新的事件循环
// 省略掉初始化新循环的列表和数组结构,同时将 old_cycle 的配置信息赋值给新的事件循环
if (ngx_cycle_modules(cycle) != NGX_OK) { // 将 ngx_modules 模块信息保存到事件循环中,其中使用的内存信息由内存池中分配
ngx_destroy_pool(pool);
return NULL;
}
// 遍历每个模块,并回调他们的 create_conf 函数
for (i = 0; cycle->modules[i]; i++) {
if (cycle->modules[i]->type != NGX_CORE_MODULE) {
continue;
}
module = cycle->modules[i]->ctx;
if (module->create_conf) {
rv = module->create_conf(cycle);
if (rv == NULL) {
ngx_destroy_pool(pool);
return NULL;
}
cycle->conf_ctx[cycle->modules[i]->index] = rv;
}
}
// 转换配置文件,并回调对应模块中设置的回调函数完成模块处理
if (ngx_conf_param(&conf) != NGX_CONF_OK) {
environ = senv;
ngx_destroy_cycle_pools(&conf);
return NULL;
}
if (ngx_conf_parse(&conf, &cycle->conf_file) != NGX_CONF_OK) {
environ = senv;
ngx_destroy_cycle_pools(&conf);
return NULL;
}
// 遍历模块文件,回调 init_conf 函数
for (i = 0; cycle->modules[i]; i++) {
if (cycle->modules[i]->type != NGX_CORE_MODULE) {
continue;
}
module = cycle->modules[i]->ctx;
if (module->init_conf) {
if (module->init_conf(cycle,
cycle->conf_ctx[cycle->modules[i]->index])
== NGX_CONF_ERROR)
{
environ = senv;
ngx_destroy_cycle_pools(&conf);
return NULL;
}
}
}
// 获取核心模块的配置信息
ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module);
// 对通过模块设置的 socket fd 启动标准三步骤:socket、bind、listen
if (ngx_open_listening_sockets(cycle) != NGX_OK) {
goto failed;
}
// 回调模块的 init_module 函数
if (ngx_init_modules(cycle) != NGX_OK) {
/* fatal */
exit(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
# ngx_single_process_cycle 函数
该函数相对简单,循环处理所有的事件循环组中的事件,当检测到 ngx_reconfigure 标志位 和 ngx_reopen 标志位时执行对应动作,检测到停止标志位,那么退出事件处理。
void ngx_single_process_cycle(ngx_cycle_t *cycle){
ngx_uint_t i;
if (ngx_set_environment(cycle, NULL) == NULL) { // 设置主循环执行环境
exit(2);
}
for (i = 0; cycle->modules[i]; i++) { // 调用模块的 init_process 回调函数
if (cycle->modules[i]->init_process) {
if (cycle->modules[i]->init_process(cycle) == NGX_ERROR) {
exit(2);
}
}
}
for ( ;; ) { // 循环处理所有事件,知道 nginx 停止
ngx_process_events_and_timers(cycle); // 本循环的核心在此,所有注意力集中到该方法
if (ngx_terminate || ngx_quit) { // 接收到停止标志位,那么回调模块 exit_process 回调函数,并退出当前循环
for (i = 0; cycle->modules[i]; i++) {
if (cycle->modules[i]->exit_process) {
cycle->modules[i]->exit_process(cycle);
}
}
ngx_master_process_exit(cycle); // 退出循环 释放占用内存
}
if (ngx_reconfigure) { // 当前指定重配置事件循环(后面分析)
...
}
if (ngx_reopen) { // 当前指定重新打开 socket(后面分析)
...
}
}
}
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
# ngx_process_events_and_timers 函数
该函数将会处理所有待执行事件,由于事件产生需要多路复用器来支撑,所以该函数将会依赖模块化设计根据运行系统来选择合适的多路复用器,读者只需要了解整体流程即可,对于多路复用器 epoll 模块的初始化 ,后面会详细描述。该函数为事件循环的核心,每个事件循环都会包含两类事件:1、时间事件 2、文件事件,当需要进行阻塞时需要根据时间事件中最小超时时间事件来决定睡眠多久。同时,该函数的核心方法为:(void) ngx_process_events(cycle, timer, flags),后文我们将分析对于epoll而言,该函数所做的操作。对于 ngx_use_accept_mutex 互斥锁而言,是为了放置多进程同时操作epoll 时,当一个连接到来,在 LT 模式下导致多个进程被唤醒,从而导致惊群现象,当我们持有互斥锁时,只有持有锁的进程才能监听 server fd。
void ngx_process_events_and_timers(ngx_cycle_t *cycle) {
ngx_uint_t flags;
ngx_msec_t timer, delta;
if (ngx_timer_resolution) { // 不执行时间事件,那么设置timer 为 -1( #define NGX_TIMER_INFINITE (ngx_msec_t) -1 )
timer = NGX_TIMER_INFINITE;
flags = 0;
} else { // 否则从保存时间事件的红黑树中找到 最近需要执行的定时器 timer
timer = ngx_event_find_timer();
flags = NGX_UPDATE_TIME; // 设置标志位为需要更新时间
}
if (ngx_use_accept_mutex) { // 当设置需要使用 接收互斥锁时,尝试获取锁(防止多进程条件下 epoll 监听server fd 导致惊群现象发生)
if (ngx_accept_disabled > 0) { // 用于多进程处理连接事件的负载均衡(当前进程的该值大于0,那么不处理连接事件)
ngx_accept_disabled--;
} else { // 否则尝试获取互斥锁
if (ngx_trylock_accept_mutex(cycle) == NGX_ERROR) {
return;
}
if (ngx_accept_mutex_held) { // 当前进程持有互斥锁那么可以执行接收事件
flags |= NGX_POST_EVENTS;
} else { // 未持有互斥锁,那么当不执行时间时间时或者待执行的时间事件的执行时间大于 ngx_accept_mutex_delay 延迟时间,那么将定时器设置为 ngx_accept_mutex_delay
if (timer == NGX_TIMER_INFINITE
|| timer > ngx_accept_mutex_delay)
{
timer = ngx_accept_mutex_delay;
}
}
}
}
if (!ngx_queue_empty(&ngx_posted_next_events)) { // 若ngx_posted_next_events待执行事件队列不为空,那么遍历其中的事件,并将它们放置到 ngx_posted_events 链表上
ngx_event_move_posted_next(cycle);
timer = 0; // 定时器事件设置为0,表示不等待,因为上述仍有未执行事件,所以当前进程不应该进行超时等待
}
delta = ngx_current_msec; // 当前时间
(void) ngx_process_events(cycle, timer, flags); // 调用 #define ngx_process_events ngx_event_actions.process_events 函数指针来执行事件,timer 用于表示可以在其中等待事件发生多长时间,而对于该事件由哪个多路复用器模块来执行,我们后文分析
delta = ngx_current_msec - delta; // 记录多路复用器执行的时间
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
"timer delta: %M", delta); // 打印日志
ngx_event_process_posted(cycle, &ngx_posted_accept_events); // 执行放置在 ngx_posted_accept_events 队列的事件
if (ngx_accept_mutex_held) { // 释放互斥锁
ngx_shmtx_unlock(&ngx_accept_mutex);
}
ngx_event_expire_timers(); // 执行当前到期的定时器事件
ngx_event_process_posted(cycle, &ngx_posted_events); // 执行前面放置在该链表中的待执行事件
}
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