nginx worker进程就是在处理网络事件、定时器事件和信号,核心是处理网络事件和定时器事件。下面看一下 worker进程是如何精确处理这些核心事件的。
在 worker进程处理函数中,首先进行 worker进程运行相关的配置初始化设置操作,然后进入无限循环,处理 worker进程关注的信号和定时器事件以及核心的网络事件。下面是相关代码:
static void ngx_worker_process_cycle(ngx_cycle_t *cycle, void *data) {
// ... 初始化相关操作
for ( ;; ) {
// ... 信号处理
// worker进程核心处理逻辑 处理事件和定时器
ngx_process_events_and_timers(cycle);
}
}
在 ngx_process_events_and_timers核心处理函数中,如果配置了 ngx_timer_resolution时间,则传给 epoll_wait的超时时间为 -1,表示永久阻塞等待,如果没有网络事件发生在 ngx_timer_resolution事件后定时器超时就会触发 ALARM信号,强制 epoll_wait返回。
如果没有配置 ngx_timer_resolution时间,nginx会调用 ngx_event_find_timer函数找到最近一个定时器超时的时间,把此时间设置为 epoll_wait的超时时间,如果没有网络事件的发生,在最近的定时器超时之前 epoll_wait函数就会返回,处理对应的超时事件。
这里看代码时一直有一个疑问,就是在调用 ngx_event_find_timer函数的时候获取了最近定时器的超时时间,如果在 epoll_wait等待期间,又添加了新的定时器比之前获取超时时间还少怎么办?如何触发刚刚添加的定时器。其实这里最开始就思考错误了,因为 nginx每个进程都是单线程的,当前进程在 epoll_wait阻塞的时候,进程是不会有地方可以添加新的定时器的。所以 ngx_event_find_timer函数返回的就是所有定时器中的最小的超时时间。
void ngx_process_events_and_timers(ngx_cycle_t *cycle) {
// ...
if (ngx_timer_resolution) {
/*
开启了时间精度 给epoll_wait的超时参数为-1 阻塞等待
epoll_wait返回有两种情况:
1、有网络事件发生
2、有信号产生 提前加好的ALARM定时器在ngx_timer_resolution时间耗尽后 触发ALARM信号
*/
timer = NGX_TIMER_INFINITE;
flags = 0;
} else {
/*
未开启高精度时钟
在定时器中找到最近一个超时定时器的时间 设置为epoll_wait的超时时间
防止长时间没有网络事件发生 epoll_wait等待时间过久 导致定时器事件延迟触发
注意:
这里之前一直有一个疑问 就是ngx_event_find_timer返回当前所有定时器中最近一个超时时间
如果epoll_wait在等待超时时间内 有其他的定时器事件加入并且超时在之前最近的事件超时之前 如何触发新加的最近的超时事件定时器
其实不会出现上面那种情况 因为nginx是单进程模型 进程阻塞在epoll_wait 不可能有地方添加定时器
*/
timer = ngx_event_find_timer();
flags = NGX_UPDATE_TIME;
}
delta = ngx_current_msec;
// 根据不同系统调用不同的网络监听接口
(void) ngx_process_events(cycle, timer, flags);
delta = ngx_current_msec - delta;
/*
ngx_process_events执行时消耗的时间delta大于0
这可能有新的定时器事件被触发 调用ngx_event_expire_timers方法处理所有满足条件的定时器事件
*/
if (delta) {
ngx_event_expire_timers();
}
// ...
}
调用 epoll_wait在参数 timer时间内等待网络事件或者信号事件的发生。
这里 epoll_wait返回后,需要判断是否需要更新 nginx的全局系统时间,如果 flags为 NGX_UPDATE_TIME,即没有 ngx_timer_resolution配置时,这里可能是最近的定时器超时了,需要更新全局系统时间,用于后续时间等的判断;或者 ngx_event_timer_alarm标志位为1,即设置了 ngx_timer_resolution配置,如果标志为1,说明是因为系统定时器超时触发的回调函数(ngx_event_process_init函数中设置)设置的此标志位,所以是 epoll_wait超时了,需要更新全局系统时间。
static ngx_int_t ngx_epoll_process_events(ngx_cycle_t *cycle, ngx_msec_t timer, ngx_uint_t flags) {
//调用epoll_wait获取事件 timer参数是在process_events调用时传入的
events = epoll_wait(ep, event_list, (int) nevents, timer);
err = (events == -1) ? ngx_errno : 0;
/*
Nginx对时间的缓存和管理 flags标志位指示要更新时间
或者ngx_event_timer_alarm标志位1 即之前ngx_event_core_module模块设置的系统定时器触发 设置了标志位
*/
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;
}
return NGX_ERROR;
}
//遍历本次epoll_wait返回的所有事件
for (i = 0; i < events; i++) {
// ...
}
// ...
}
在核心事件模块 ngx_event_core_module的初始化函数 ngx_event_process_init中,会对定时器进行初始化。
如果配置中设置了 ngx_timer_resolution参数,则设置指定时间的定时器,保证最长 ngx_timer_resolution就会触发信号,强制让 epoll_wait函数返回,即使没有网络事件的发生。
static ngx_int_t ngx_event_process_init(ngx_cycle_t *cycle) {
// ...
//初始化红黑树实现的定时器
if (ngx_event_timer_init(cycle->log) == NGX_ERROR) {
return NGX_ERROR;
}
/*
配置文件中设置了控制时间精度 根据配置时间设置指定时间的定时器
这里是配合epoll_wait使用 开启了指定时间精度 epoll_wait会阻塞等待 直到有网络事件发生或者到了定时器指定的时间 发生信号导致epoll_wait返回
*/
if (ngx_timer_resolution && !(ngx_event_flags & NGX_USE_TIMER_EVENT)) {
struct sigaction sa;
struct itimerval itv;
ngx_memzero(&sa, sizeof(struct sigaction));
sa.sa_handler = ngx_timer_signal_handler;
sigemptyset(&sa.sa_mask);
if (sigaction(SIGALRM, &sa, NULL) == -1) {
return NGX_ERROR;
}
itv.it_interval.tv_sec = ngx_timer_resolution / 1000;
itv.it_interval.tv_usec = (ngx_timer_resolution % 1000) * 1000;
itv.it_value.tv_sec = ngx_timer_resolution / 1000;
itv.it_value.tv_usec = (ngx_timer_resolution % 1000 ) * 1000;
if (setitimer(ITIMER_REAL, &itv, NULL) == -1) {
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno, "setitimer() failed");
}
}
// ...
}
定时器超时后回调的函数就是 ngx_timer_signal_handler,非常简单就是设置 ngx_event_timer_alarm标志位1。这个标识会在其他地方在更新时间的时候使用到。
static void ngx_timer_signal_handler(int signo) {
ngx_event_timer_alarm = 1;
}