nginx worker進程就是在處理網絡事件、定時器事件和信號,核心是處理網絡事件和定時器事件。下面看一下 worker進程是如何精確處理這些核心事件的。
1、worker進程啓動
在 worker進程處理函數中,首先進行 worker進程運行相關的配置初始化設置操作,然後進入無限循環,處理 worker進程關注的信號和定時器事件以及核心的網絡事件。下面是相關代碼:
static void ngx_worker_process_cycle(ngx_cycle_t *cycle, void *data) {
// ... 初始化相關操作
for ( ;; ) {
// ... 信號處理
// worker進程核心處理邏輯 處理事件和定時器
ngx_process_events_and_timers(cycle);
}
}
2、worker進程核心處理函數
在 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();
}
// ...
}
3、網絡事件處理函數ngx_process_events
調用 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++) {
// ...
}
// ...
}
4、核心事件模塊初始化init_process
在覈心事件模塊 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;
}