nginx事件处理流程
1. 进程初始化
nginx启动流程中已经对进程启动进行了简单介绍,现在对每种进程的初始化进行下介绍。
(1) 总进程的初始化(这里还没有启动worker进程,所以称为总进程):
作为整个进程的入口,很多公用的初始化都是在一开始完成的,先对debug(ngx_debug_init)和错误码进行初始化(ngx_strerror_init),然后解析nginx的启动命令参数,并根据参数进行进一步处理;时间相关参数初始化ngx_time_init(),如果支持正则表达式,则初始化regex相关参数ngx_regex_init,紧接着是日志相关参数(日志文件描述符等)初始化ngx_log_init,如果支持openssl,则对ssl相关参数初始化ngx_ssl_init。
为init_cycle创建资源池pool,将ngin命令参数保存为全局变量,处理init_cycle的选项,初始化一些全局变量,最后根据系统初始化系统相关全局变量。
初始化crc32表,然后调用ngx_add_inherited_sockets函数,这个函数有点复杂,一会单讲,继续向下,为模块编号,根据之前的init_cycle生成一个cycle指针保存所有的ngx_cycle_s数据, 初始化信号ngx_init_signals,将信号与处理函数连接起来,根据daemon配置参数启动daemon进程,创建进程号文件,最后根据ngx_process判断进入两种进程模式ngx_single_process_cycle和ngx_master_process_cycle。
(2) Master进程的初始化(启动worker进程后,原进程称为master进程):
先初始化信号集,向信号集set中添加各种需要响应的信号,并通过sigprocmask(SIG_BLOCK, &set, NULL)将信号集set加入原有的进程阻塞信号集中,然后清空信号集set,设置进程title,获取core模块的配置信息,启动worker进程和cache管理进程,进入无限for(;;)循环。
(3) Worker进程的初始化:
Worker进程的初始化主要在ngx_worker_process_init函数中完成,设置环境变量ngx_set_environment,获取core模块的配置参数ngx_get_conf,进程优先级设置setpriority,如果设置了worker_rlimit_nofile和worker_rlimit_core,则根据这两个值分别设置进程的RLIMIT_NOFILE和RLIMIT_CORE参数,并根据系统是否支持RLIMIT_SIGPENDING来配置用户可用的最大挂起信号数。然后根据当前的有效用户设置进程的userID和groupID,根据配置文件决定是否进行CPU绑定ngx_setaffinity,更改默认路径chdir,清空信号集set,通过sigprocmask清空进程的阻塞信号集,保证worker进程不会被信号中断。关闭该进程多余的channel。最后根据全局变量ngx_channel开启一个通道,该通道只处理读事件,处理函数为ngx_channel_handler,有点明显了,这个通道是用来与master进程进行通信的,因为只有跟master进程通信,worker才会只收不发。
for (i = 0; ngx_modules[i]; i++) { if (ngx_modules[i]->init_process) { if (ngx_modules[i]->init_process(cycle) == NGX_ERROR) { /* fatal */ exit(2); } } }
代码4-1
这段代码是对每个模块的处理初始化,遍历所有模块,对有处理初始化函数的模块进行处理,其中会调用ngx_event_process_init函数对事件进行初始化,将事件添加到epoll信号集中,代码如下:
rev->handler = ngx_event_accept; if (ngx_use_accept_mutex) { continue; } if (ngx_event_flags & NGX_USE_RTSIG_EVENT) { if (ngx_add_conn(c) == NGX_ERROR) { return NGX_ERROR; } } else { if (ngx_add_event(rev, NGX_READ_EVENT, 0) == NGX_ERROR) { return NGX_ERROR; } }
代码4-2
将在配置文件解析过程中的http模块初始化中建立的listen数组加入到epoll监控事件集中,并把处理函数handler挂载处理函数ngx_event_accept,结合下面的《模块初始化》可以体现整个的初始化过程。
(4) ngx_add_inherited_sockets函数:
纠结了段时间,虽然能看懂代码逻辑,但实在搞不清实际用途,不过在查阅了很多网上资料后,终于明白了,原来适用于平滑升级的(感谢http://blog.csdn.net/dingyujie/article/details/7192144的一些提示)。
该函数是将环境变量NGINX的值解析为sockets,将解析出的合法socket number加入listen数组,这些sockets以“:”或“;”分隔,并将全局变量ngx_inherited置为1,最后使用ngx_set_inherited_sockets对每个socket的参数进行赋值,原理简单,使用getsockname函数通过socket获取该连接的地址信息等,将结果放入listen的结构中,通过一系列的初始化,最终成为一组可用的listen数组。
关于这个函数在整个代码中的用途,请看后面的《nginx平滑升级》。
2. 模块初始化
(1) 结构体介绍
模块的结构体ngx_module_s定义如下:
struct ngx_module_s { ngx_uint_t ctx_index; //分类模块计数器 ngx_uint_t index; //模块计数器 ngx_uint_t spare0; ngx_uint_t spare1; ngx_uint_t spare2; ngx_uint_t spare3; ngx_uint_t version; //版本 void *ctx; //该模块的上下文,每个种类的模块有不同的上下文 ngx_command_t *commands; //该模块的命令集,指向一个ngx_command_t数组 ngx_uint_t type; //该模块的种类,为core/event/http/mail中的一种 ngx_int_t (*init_master)(ngx_log_t *log); //初始化master ngx_int_t (*init_module)(ngx_cycle_t *cycle); //初始化模块 ngx_int_t (*init_process)(ngx_cycle_t *cycle); //初始化工作进程 ngx_int_t (*init_thread)(ngx_cycle_t *cycle); //初始化线程 void (*exit_thread)(ngx_cycle_t *cycle); //退出线程 void (*exit_process)(ngx_cycle_t *cycle); //退出工作进程 void (*exit_master)(ngx_cycle_t *cycle); //退出master uintptr_t spare_hook0; uintptr_t spare_hook1; uintptr_t spare_hook2; uintptr_t spare_hook3; uintptr_t spare_hook4; uintptr_t spare_hook5; uintptr_t spare_hook6; uintptr_t spare_hook7; };
代码4-3
下面是对核心模块的结构体对象进行初始化,采用了两个宏来设置前7个字段和后8个字段。
#define NGX_MODULE_V1 0, 0, 0, 0, 0, 0, 1 //该宏用来初始化前7个字段 #define NGX_MODULE_V1_PADDING 0, 0, 0, 0, 0, 0, 0, 0 //该宏用来初始化最后8个字段 static ngx_core_module_t ngx_core_module_ctx = { ngx_string("core"), ngx_core_module_create_conf, ngx_core_module_init_conf }; ngx_module_t ngx_core_module = { NGX_MODULE_V1, &ngx_core_module_ctx, /* module context */ ngx_core_commands, /* module directives */ NGX_CORE_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 };
代码4-4
(2) 初始化模块
上面说明了两个结构体,下面开始对模块初始化流程进行介绍。在main函数中,对所有的模块进行排序。
ngx_max_module = 0; for (i = 0; ngx_modules[i]; i++) { ngx_modules[i]->index = ngx_max_module++; }
代码4-5
紧接着就是ngx_init_cycle函数,该函数完成了对模块初始化的所有调用。该函数对时间、资源池、各种链表、数组和队列等进程初始化,如果指定了create_conf函数指针,就调用该函数。ngx_conf_parse是该函数中最为关键一个函数,它对模块进行了初始化操作(ngx_conf_param函数最终也会调用ngx_conf_parse函数进行配置解析)。
if (filename) { /* open configuration file */ fd = ngx_open_file(filename->data, NGX_FILE_RDONLY, NGX_FILE_OPEN, 0); if (fd == NGX_INVALID_FILE) { ngx_conf_log_error(NGX_LOG_EMERG, cf, ngx_errno, ngx_open_file_n " \"%s\" failed", filename->data); return NGX_CONF_ERROR; } prev = cf->conf_file; cf->conf_file = &conf_file; if (ngx_fd_info(fd, &cf->conf_file->file.info) == -1) { ngx_log_error(NGX_LOG_EMERG, cf->log, ngx_errno, ngx_fd_info_n " \"%s\" failed", filename->data); } cf->conf_file->buffer = &buf; buf.start = ngx_alloc(NGX_CONF_BUFFER, cf->log); if (buf.start == NULL) { goto failed; } buf.pos = buf.start; buf.last = buf.start; buf.end = buf.last + NGX_CONF_BUFFER; buf.temporary = 1; cf->conf_file->file.fd = fd; cf->conf_file->file.name.len = filename->len; cf->conf_file->file.name.data = filename->data; cf->conf_file->file.offset = 0; cf->conf_file->file.log = cf->log; cf->conf_file->line = 1; type = parse_file; } else if (cf->conf_file->file.fd != NGX_INVALID_FILE) { type = parse_block; } else { type = parse_param; }
代码4-6
这段代码主要是根据不同的解析方式,将变量type置为相应的类型。如果是解析配置文件,则打开文件,并将缓存区挂到ngx_conf_t结构体上。
后面就会进入一个for循环,对数据和文件的解析一般都采用这种方式,在for循环中,先通过ngx_conf_read_token解析出记号,根据返回的结果是否为NGX_OK或NGX_CONF_BLOCK_START判断是否跳过执行handler函数。
代码中有一段注释说明ngx_conf_read_token函数返回值的意义:
/* ngx_conf_read_token() may return * NGX_ERROR there is error * NGX_OK the token terminated by ";" was found * NGX_CONF_BLOCK_START the token terminated by "{" was found * NGX_CONF_BLOCK_DONE the "}" was found * NGX_CONF_FILE_DONE the configuration file is done */
代码4-7
由于没有对handler挂载函数,所以调用后面的ngx_conf_handler函数进行默认处理。
Ngx_conf_handler函数中,会遍历所有的模块,而每个模块都有多个命令组成的命令数组,所以在遍历模块的循环中,还有一个遍历当前模块的命令数组的循环。针对于某个模块的某个命令,通过下面这段代码判断是否符合条件(主要对判断命令名、模块类型和命令类型进行判断),对符合条件的情况进行进一步处理:
if (name->len != cmd->name.len) { continue; } if (ngx_strcmp(name->data, cmd->name.data) != 0) { continue; } found = 1; if (ngx_modules[i]->type != NGX_CONF_MODULE && ngx_modules[i]->type != cf->module_type) { continue; } /* is the directive's location right ? */ if (!(cmd->type & cf->cmd_type)) { continue; }
代码4-8
图:配置文件解析流程
然后就是判断指令的参数个数是否正确,如果不正确goto invalid;指定指令的配置环境,调用ngx_command_s结构中的set函数指针cmd->set(cf, cmd, conf)。该函数指针根据不同的命令指向不同的处理函数,而当前配置文件的模块类型module_type为NGX_CORE_MODULE,决定了当前的正在进行配置的模块为ngx_core_module,命令数组为ngx_core_commands。
举个例子,比如在解析ngx_events_block函数时,里面还会调用ngx_conf_parse函数进行配置文件解析。这是由于在解析文件时,采用了递归的方法,当解析到某个关键值时,调用ngx_conf_parse函数,然后剩下的配置信息在该ngx_conf_parse调用的command->set函数指针中继续进行解析,直到解析完这个模块的配置,才会跳到最外层的循环,继续其它模块的配置文件解析。
解析完成后,会根据配置的信息创建路径、打开文件、创建共享存储区、打开socket进行监听、设置socket选项、关闭不必要的文件、关闭不必要的socket、释放不必要的共享存储区等操作。
(3) http模块初始化
图:http模块初始化流程
由于nginx大多数是作为http服务器运行的,所以对http模块的初始化着重介绍一下。
ngx_http_commands结构体的set函数指针挂载了函数ngx_http_block函数,该函数是http模块的配置初始化函数。
和其它主要模块初始化一样,在开始处先对http模块中每个子模块进行编号,然后给配置上下文分配空间并初始化,解析http块,遍历所有的server块,初始化phases,最后两个函数及其重要,ngx_http_init_phase_handlers将配置文件对应的checker和handler挂载上对应的函数加入到配置上下文中,ngx_http_optimize_servers则会对所有配置的端口进行socket建立、绑定和监听,并将listen对应的结构体加入到listens数组中。
1. 无限for()循环
(1) Master进程
Master进程的主要工作是处理用户命令,管理worker进程。用户命令是通过信号传递给进程的,进程在接收到信号后会对信号进行处理。在main函数中,有对信号处理的初始化ngx_init_signals,将signals数组的信号及其处理函数通过sigaction连接起来。一旦用户执行命令,则会发出对应的信号,会触发ngx_signal_handler函数进行信号处理。
与该进程信号有关的全局变量有如下几个:ngx_quit,ngx_terminate,ngx_noaccept,ngx_reconfigure,ngx_reopen,ngx_change_binary,ngx_sigalrm ,ngx_sigio和ngx_reap。
nginx命令及与全局变量的对应关系:
命令介绍 |
命令 |
全局变量 |
备注 |
从容停止Nginx |
kill -QUIT 主进程号 |
nginx_quit |
|
快速停止Nginx |
kill -TERM 主进程号 |
nginx_terminate |
|
平滑重启命令 |
kill -HUP 主进程号或进程号文件路径或 /usr/nginx/sbin/nginx -s reload |
ngx_reconfigure |
|
平滑升级1: |
kill -USR2 旧版程序的主进程号或进程文件名 |
ngx_change_binary |
旧的Nginx主进程将会把自己的进程文件改名为.oldbin,然后执行新版 Nginx。新旧Nginx会同市运行,共同处理请求。 |
平滑升级2: |
kill -WINCH 旧版主进程号 |
ngx_noaccept |
慢慢旧的工作进程就都会随着任务执行完毕而退出,新版的Nginx的工作进程会逐渐取代旧版 工作进程。 |
重新打开日志文件 |
kill –USR1进程号 或 nginx -s reopen |
ngx_reopen |
|
定时器超时信号 |
SIGALRM |
ngx_sigalrm |
|
子进程关闭 |
SIGCHLD |
ngx_reap |
子进程关闭向父进程发送该信号。 |
表4-1:nginx命令介绍与全局变量关系
通过表4-1了解了nginx支持的命令及信号,结合代码来进一步进行解释。
if (delay) { if (ngx_sigalrm) { sigio = 0; delay *= 2; ngx_sigalrm = 0; } ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0, "termination cycle: %d", delay); itv.it_interval.tv_sec = 0; itv.it_interval.tv_usec = 0; itv.it_value.tv_sec = delay / 1000; itv.it_value.tv_usec = (delay % 1000 ) * 1000; if (setitimer(ITIMER_REAL, &itv, NULL) == -1) { ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno, "setitimer() failed"); } } sigsuspend(&set); ngx_time_update();
代码4-9
如果delay不为0,即需要延迟delay,如果是定时器超时(ngx_sigalrm为1),则delay翻倍,继续设置定时器setitimer。
sigsuspend挂起,不阻塞任何信号,等待任何信号(因为set信号集为空)到达后恢复正常,由于有信号触发,所以先要执行ngx_signal_handler。
ngx_time_update将更新当前时间的全局变量以及cache相关的变量。
if (ngx_reap) { ngx_reap = 0; ngx_log_debug0(NGX_LOG_DEBUG_EVENT, cycle->log, 0, "reap children"); live = ngx_reap_children(cycle); } if (!live && (ngx_terminate || ngx_quit)) { ngx_master_process_exit(cycle); }
代码4-10
ngx_reap为1,表示有子进程退出,会调用ngx_reap_children进行进一步处理,在该函数中,会根据用户命令(进程是否退出)决定是否重启关闭的worker进程。如果没有运行的worker进程了,则live为0。如果用户关闭进程,则在此处可以关闭master进程了。
if (ngx_terminate) { if (delay == 0) { delay = 50; } if (sigio) { sigio--; continue; } sigio = ccf->worker_processes + 2 /* cache processes */; if (delay > 1000) { ngx_signal_worker_processes(cycle, SIGKILL); } else { ngx_signal_worker_processes(cycle, ngx_signal_value(NGX_TERMINATE_SIGNAL)); } continue; }
代码4-11
ngx_terminate为1,通过delay设置延迟时间,当delay超过1000时,则强制关闭worker进程,否则,快速结束worker进程,与子进程的通信通过ngx_signal_worker_processes函数进行。
if (ngx_quit) { ngx_signal_worker_processes(cycle, ngx_signal_value(NGX_SHUTDOWN_SIGNAL)); ls = cycle->listening.elts; for (n = 0; n < cycle->listening.nelts; n++) { if (ngx_close_socket(ls[n].fd) == -1) { ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_socket_errno, ngx_close_socket_n " %V failed", &ls[n].addr_text); } } cycle->listening.nelts = 0; continue; }
代码4-12
ngx_quit为1,则通知worker进程从容的关闭进程,并将listen监听数组中的每个链接关闭。
if (ngx_reconfigure) { ngx_reconfigure = 0; if (ngx_new_binary) { ngx_start_worker_processes(cycle, ccf->worker_processes, NGX_PROCESS_RESPAWN); ngx_start_cache_manager_processes(cycle, 0); ngx_noaccepting = 0; continue; } ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "reconfiguring"); cycle = ngx_init_cycle(cycle); if (cycle == NULL) { cycle = (ngx_cycle_t *) ngx_cycle; continue; } ngx_cycle = cycle; ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module); ngx_start_worker_processes(cycle, ccf->worker_processes, NGX_PROCESS_JUST_RESPAWN); ngx_start_cache_manager_processes(cycle, 1); /* allow new processes to start */ ngx_msleep(100); live = 1; ngx_signal_worker_processes(cycle, ngx_signal_value(NGX_SHUTDOWN_SIGNAL)); }
代码4-13
如果ngx_reconfigure为1,则表示要重新载入配置文件,这个过程其实是启动一个新的master进程,然后关闭旧的master进程。如果ngx_new_binary为1,表示为平滑升级过程,直接启动worker进程ngx_start_worker_processes和cache管理进程ngx_start_cache_manager_processes;否则就是普通的重载配置文件操作。会初始化cycle,启动worker进程和cache管理进程,通知worker进程关闭,这里对worker进程启动了又关闭,可能是启动了已经被关闭的worker进程,而始终活着的worker没有机会重载配置文件,所以最后会把worker都关闭,并通过SIGCHLD信号重启所有worker。
if (ngx_restart) { ngx_restart = 0; ngx_start_worker_processes(cycle, ccf->worker_processes, NGX_PROCESS_RESPAWN); ngx_start_cache_manager_processes(cycle, 0); live = 1; }
代码4-14
ngx_reap为1,表示有子进程退出,会调用ngx_reap_children进行进一步处理,在该函数中,会根据用户命令(进程是否退出)决定是否重启关闭的worker进程。如果没有运行的worker进程了,则live为0。如果用户关闭进程,则在此处可以关闭master进程了。
if (ngx_terminate) { if (delay == 0) { delay = 50; } if (sigio) { sigio--; continue; } sigio = ccf->worker_processes + 2 /* cache processes */; if (delay > 1000) { ngx_signal_worker_processes(cycle, SIGKILL); } else { ngx_signal_worker_processes(cycle, ngx_signal_value(NGX_TERMINATE_SIGNAL)); } continue; }
代码4-15
ngx_terminate为1,通过delay设置延迟时间,当delay超过1000时,则强制关闭worker进程,否则,快速结束worker进程,与子进程的通信通过ngx_signal_worker_processes函数进行。
if (ngx_quit) { ngx_signal_worker_processes(cycle, ngx_signal_value(NGX_SHUTDOWN_SIGNAL)); ls = cycle->listening.elts; for (n = 0; n < cycle->listening.nelts; n++) { if (ngx_close_socket(ls[n].fd) == -1) { ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_socket_errno, ngx_close_socket_n " %V failed", &ls[n].addr_text); } } cycle->listening.nelts = 0; continue; }
代码4-16
ngx_quit为1,则通知worker进程从容的关闭进程,并将listen监听数组中的每个链接关闭。
if (ngx_reconfigure) { ngx_reconfigure = 0; if (ngx_new_binary) { ngx_start_worker_processes(cycle, ccf->worker_processes, NGX_PROCESS_RESPAWN); ngx_start_cache_manager_processes(cycle, 0); ngx_noaccepting = 0; continue; } ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "reconfiguring"); cycle = ngx_init_cycle(cycle); if (cycle == NULL) { cycle = (ngx_cycle_t *) ngx_cycle; continue; } ngx_cycle = cycle; ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module); ngx_start_worker_processes(cycle, ccf->worker_processes, NGX_PROCESS_JUST_RESPAWN); ngx_start_cache_manager_processes(cycle, 1); /* allow new processes to start */ ngx_msleep(100); live = 1; ngx_signal_worker_processes(cycle, ngx_signal_value(NGX_SHUTDOWN_SIGNAL)); }
代码4-17
如果ngx_reconfigure为1,则表示要重新载入配置文件,这个过程其实是启动一个新的master进程,然后关闭旧的master进程。如果ngx_new_binary为1,表示为平滑升级过程,直接启动worker进程ngx_start_worker_processes和cache管理进程ngx_start_cache_manager_processes;否则就是普通的重载配置文件操作。会初始化cycle,启动worker进程和cache管理进程,通知worker进程关闭,这里对worker进程启动了又关闭,可能是启动了已经被关闭的worker进程,而始终活着的worker没有机会重载配置文件,所以最后会把worker都关闭,并通过SIGCHLD信号重启所有worker。
if (ngx_restart) { ngx_restart = 0; ngx_start_worker_processes(cycle, ccf->worker_processes, NGX_PROCESS_RESPAWN); ngx_start_cache_manager_processes(cycle, 0); live = 1; }
代码4-18
这段代码没有找到对应的命令,不知道在何时能够触发。
if (ngx_reopen) { ngx_reopen = 0; ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "reopening logs"); ngx_reopen_files(cycle, ccf->user); ngx_signal_worker_processes(cycle, ngx_signal_value(NGX_REOPEN_SIGNAL)); }
代码4-19
Ngx_reopen为1,是与重新打开日志文件有关。
if (ngx_change_binary) { ngx_change_binary = 0; ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "changing binary"); ngx_new_binary = ngx_exec_new_binary(cycle, ngx_argv); } if (ngx_noaccept) { ngx_noaccept = 0; ngx_noaccepting = 1; ngx_signal_worker_processes(cycle, ngx_signal_value(NGX_SHUTDOWN_SIGNAL)); }
代码4-20
这段代码与平滑升级相关,在后面的《平滑升级》章节具体介绍。
(2) Worker进程
Worker进程的for循环就简单多了,毕竟它不像master进程需要管理很多进程。从源码中可以看出,worker进程除了ngx_process_events_and_timers外只需要处理三种情况,完成四种操作。
if (ngx_exiting) { c = cycle->connections; for (i = 0; i < cycle->connection_n; i++) { /* THREAD: lock */ if (c[i].fd != -1 && c[i].idle) { c[i].close = 1; c[i].read->handler(c[i].read); } } if (ngx_event_timer_rbtree.root == ngx_event_timer_rbtree.sentinel) { ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "exiting"); ngx_worker_process_exit(cycle); } }
代码4-21
ngx_exiting为1 表示进程正在退出,在该进程选择从容退出时,即ngx_quit为1后,才会进入该状态。该过程时将所有的connections连接关闭,当事件对应的红黑树中没有计时器时,可以快速退出该进程ngx_worker_process_exit。
if (ngx_terminate) { ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "exiting"); ngx_worker_process_exit(cycle); }
代码4-22
ngx_terminate为1表示快速关闭进程,源码中有四个对该变量置为1的地方,其中一个是用于Master进程或Single进程的,一个适用于线程,其余两个用户Worker进程。当进程收到信号NGX_TERMINATE_SIGNAL和SIGINT时,以及通过channel接收到NGX_CMD_TERMINATE信号ngx_channel_handler。进程会快速的退出ngx_worker_process_exit。
if (ngx_quit) { ngx_quit = 0; ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "gracefully shutting down"); ngx_setproctitle("worker process is shutting down"); if (!ngx_exiting) { ngx_close_listening_sockets(cycle); ngx_exiting = 1; } }
代码4-23
Ngx_quit为1表示进程从容退出,如果ngx_exiting为0,则关闭监听的socket,并将ngx_exiting置为1。
if (ngx_reopen) { ngx_reopen = 0; ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "reopening logs"); ngx_reopen_files(cycle, -1); }
代码4-24
Ngx_reopen为1表示重新打开日志文件。
3. 事件处理ngx_process_events_and_timers
Ngx_process_events_and_timers函数只在worker进程中出现,因为只有worker进程才真正的处理用户请求。
if (ngx_timer_resolution) { timer = NGX_TIMER_INFINITE; flags = 0; } else { timer = ngx_event_find_timer(); flags = NGX_UPDATE_TIME; #if (NGX_THREADS) if (timer == NGX_TIMER_INFINITE || timer > 500) { timer = 500; } #endif }
代码4-25
该函数一开始就先进行ngx_timer_resolution判断,这个值在数据结构ngx_core_conf_t里介绍过,为了减少调用gettimeofdate的次数。如果配置了该值,则timer置为NGX_TIMER_INFINITE;如果没配置,通过ngx_event_find_timer函数将事件红黑树中最早超时的计时器时间与当前时间的差值赋给timer,并将flags的NGX_UPDATE_TIME置位。
然后是关于进程间互斥和负载均衡的一段代码,将在后面的《nginx进程间的锁》中详细介绍,暂时先跳过去。
delta = ngx_current_msec; (void) ngx_process_events(cycle, timer, flags); delta = ngx_current_msec - delta;
代码4-26
ngx_process_events函数会根据采用的不同事件模式来挂载不同的函数,由于目前linux/unix大都支持epoll,所以选择epoll模式。该函数会挂载ngx_epoll_process_events函数,用于处理epoll事件。
Ngx_epoll_process_events函数wait所有的epoll事件event_list,并对发生的事件做相应的处理,EPOLLIN事件则响应读操作或者加入POST队列延迟处理,EPOLLOUT事件则响应写操作或加入POST队列延迟处理。
static ngx_int_t ngx_epoll_process_events(ngx_cycle_t *cycle, ngx_msec_t timer, ngx_uint_t flags) { int events; uint32_t revents; ngx_int_t instance, i; ngx_uint_t level; ngx_err_t err; ngx_event_t *rev, *wev, **queue; ngx_connection_t *c; /* NGX_TIMER_INFINITE == INFTIM */ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0, "epoll timer: %M", timer); /*epoll_wait监听所有注册的epoll事件,timer则是代码4-13中计算出的超时时间,返回发生的事件集events*/ events = epoll_wait(ep, event_list, (int) nevents, timer); err = (events == -1) ? ngx_errno : 0; if (flags & NGX_UPDATE_TIME || ngx_event_timer_alarm) { ngx_time_update(); } if (err) { if (err == NGX_EINTR) { if (ngx_event_timer_alarm) { ngx_event_timer_alarm = 0; return NGX_OK; } level = NGX_LOG_INFO; } else { level = NGX_LOG_ALERT; } ngx_log_error(level, cycle->log, err, "epoll_wait() failed"); return NGX_ERROR; } /*如果events为0,则没有事件发生*/ if (events == 0) { if (timer != NGX_TIMER_INFINITE) { return NGX_OK; } ngx_log_error(NGX_LOG_ALERT, cycle->log, 0, "epoll_wait() returned no events without timeout"); return NGX_ERROR; } ngx_mutex_lock(ngx_posted_events_mutex); /*遍历发生的事件集,对每个事件做相对处理*/ for (i = 0; i < events; i++) { c = event_list[i].data.ptr; instance = (uintptr_t) c & 1; c = (ngx_connection_t *) ((uintptr_t) c & (uintptr_t) ~1); /*rec事件为当前连接的读事件*/ rev = c->read; if (c->fd == -1 || rev->instance != instance) { /*在这次迭代中刚刚关闭的文件描述符的旧事件*/ /* * the stale event from a file descriptor * that was just closed in this iteration */ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0, "epoll: stale event %p", c); continue; } /*revents为该事件的标识*/ revents = event_list[i].events; ngx_log_debug3(NGX_LOG_DEBUG_EVENT, cycle->log, 0, "epoll: fd:%d ev:%04XD d:%p", c->fd, revents, event_list[i].data.ptr); if (revents & (EPOLLERR|EPOLLHUP)) { ngx_log_debug2(NGX_LOG_DEBUG_EVENT, cycle->log, 0, "epoll_wait() error on fd:%d ev:%04XD", c->fd, revents); } if ((revents & (EPOLLERR|EPOLLHUP)) && (revents & (EPOLLIN|EPOLLOUT)) == 0) { /*如果错误事件没有EPOLLIN或EPOLLOUT标识,则将这两个标识加入到事件中,以保证至少有一个处理函数处理该事件*/ revents |= EPOLLIN|EPOLLOUT; } if ((revents & EPOLLIN) && rev->active) { /*如果是EPOLLIN事件,并且是active的*/ if ((flags & NGX_POST_THREAD_EVENTS) && !rev->accept) { rev->posted_ready = 1; } else { rev->ready = 1; } if (flags & NGX_POST_EVENTS) { /*当前进程需要推迟事件处理,则将事件加入延迟队列中。如果是accept事件,则加入ngx_posted_accept_events 队列,否则加入ngx_posted_events队列 */ queue = (ngx_event_t **) (rev->accept ? &ngx_posted_accept_events : ngx_posted_events); ngx_locked_post_event(rev, queue); } else { /*如果不延迟处理,则马上调用对应的处理函数*/ rev->handler(rev); } } /*rec事件为当前连接的写事件*/ wev = c->write; if ((revents & EPOLLOUT) && wev->active) { /*如果是EPOLLOUT事件,并且是active的*/ if (c->fd == -1 || wev->instance != instance) { ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0, "epoll: stale event %p", c); continue; } if (flags & NGX_POST_THREAD_EVENTS) { wev->posted_ready = 1; } else { wev->ready = 1; } if (flags & NGX_POST_EVENTS) { /*当前进程需要推迟事件处理,则将事件加入延迟队列中。由于写操作不会有accept事件,所以没有ngx_posted_accept_events 队列的操作*/ ngx_locked_post_event(wev, &ngx_posted_events); } else { /*如果不延迟处理,则马上调用对应的处理函数*/ wev->handler(wev); } } } ngx_mutex_unlock(ngx_posted_events_mutex); return NGX_OK; }
代码4-27
到此,可以看出,剩下的处理就是对handler挂载函数的调用了。
具体的信号处理在后面介绍,主要是用了epoll模式(关于epoll的介绍可以参看之前的《linux-epoll研究》)。