參考資料<深入理解Nginx>
根據不同的系統內核,Nginx會使用不同的事件驅動機制,本次描述的場景是使用epoll來驅動事件的處理。
epoll的使用方法
1.int epoll_create(int size);
epoll_create返回一個句柄,之后epoll的使用將依靠這個句柄來標識。參數size只是告訴epoll所要處理的大致事件數目,一些內核版本的實現中,這個參數沒有任何意義。
2.int epoll_ctl(int epfd,int op,int fd,struct epoll_event *event);
epoll_ctl向epoll對象中添加、修改或者刪除感興趣的事件,返回0表示成功,否則返回-1。
參數epfd是epoll_create方法返回的句柄,而op參數的意義如下表
參數fd是待檢測的連接套接字,第四個參數是在告訴epoll對什么樣的事件感興趣,它使用的epoll_event結構體定義如下
struct epoll_event { _uint32_t events; epoll_data_t data; };
events取值如下表
而data成員是一個epoll_data聯合
typedef union epoll_data { void *ptr; int fd; uint32_t u32; uint64_t u64; } epoll_data_t;
這個data成員還與具體的使用方式相關。例如,ngx_epoll_module模塊使用了給ptr成員,作為指向ngx_connection_t連接的指針。
3.int epoll_wait(int fd,struct epoll_event *events,int maxevents,int timeout);
第一個參數epfd是epoll的描述符。第二個參數events則是分配好的epoll_event結構體數組,epoll將會把發生的事件賦值到events數組中,
第三個參數表示本次可以返回的最大事件數目,第四個參數表示在沒有檢測到時間發生時最多等待的時間。
ngx_epoll_module模塊
該模塊使用如下的上下文結構
ngx_event_module_t ngx_epoll_module_ctx = { &epoll_name, ngx_epoll_create_conf, /* create configuration */ ngx_epoll_init_conf, /* init configuration */ { 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 */ NULL, /* process the changes */ ngx_epoll_process_events, /* process the events */ ngx_epoll_init, /* init the events */ ngx_epoll_done, /* done the events */ } };
在Nginx的啟動過程中。ngx_epoll_init方法將會被調用,它主要做了兩件事情:
1.調用epoll_create方法創建epoll對象。
2.創建event_list數組,用於進行epoll_wait調用時傳遞內核態的事件。
ngx_epoll_add_event通過調用epoll_ctl向epoll中添加或者刪除事件。可以通過這個函數的部分源碼來了解一下該過程

1 static ngx_int_t 2 ngx_epoll_add_event(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags) 3 { 4 int op; 5 uint32_t events, prev; 6 ngx_event_t *e; 7 ngx_connection_t *c; 8 struct epoll_event ee; 9 10 //每個事件的data成員都存放着其對應的ngx_connection_t連接 11 c = ev->data; 12 13 //確定當前時間是讀事件還是寫事件 14 events = (uint32_t) event; 15 16 ... 17 //根據active標志位確定是否為活躍事件,以決定到底是修改還是添加事件 18 if (e->active) { 19 op = EPOLL_CTL_MOD; 20 events |= prev; 21 22 } else { 23 op = EPOLL_CTL_ADD; 24 } 25 26 ee.events = events | (uint32_t) flags; 27 ee.data.ptr = (void *) ((uintptr_t) c | ev->instance); 28 29 //調用epoll_ctl方法向epoll中添加事件 30 if (epoll_ctl(ep, op, c->fd, &ee) == -1) { 31 ngx_log_error(NGX_LOG_ALERT, ev->log, ngx_errno, 32 "epoll_ctl(%d, %d) failed", op, c->fd); 33 return NGX_ERROR; 34 } 35 36 //標識為活躍事件 37 ev->active = 1; 38 39 return NGX_OK; 40 }
同理,ngx_epoll_del_event方法也通過類似的方式刪除事件。
對於ngx_epoll_add_connection和ngx_epoll_del_connection方法,也是調用epoll_ctl進行處理,只是每一個連接都對應讀/寫事件。
ngx_epoll_process_events方法則調用epoll_wait來獲取事件並且處理事件,下面是該函數的部分源碼

1 static ngx_int_t 2 ngx_epoll_process_events(ngx_cycle_t *cycle, ngx_msec_t timer, ngx_uint_t flags) 3 { 4 int events; 5 uint32_t revents; 6 ngx_int_t instance, i; 7 ngx_event_t *rev, *wev, **queue; 8 ngx_connection_t *c; 9 10 11 //一開始就是等待事件,最長等待時間為timer 12 events = epoll_wait(ep, event_list, (int) nevents, timer); 13 14 ... 15 //循環開始處理收到的所有事件 16 for (i = 0; i < events; i++) { 17 //ptr成員指向的是ngx_connection_t,但最后一位有特殊含義,需要將它屏蔽掉 18 c = event_list[i].data.ptr; 19 instance = (uintptr_t) c & 1; 20 c = (ngx_connection_t *) ((uintptr_t) c & (uintptr_t) ~1); 21 22 //取出讀事件 23 rev = c->read; 24 ... 25 //取得發生一個事件 26 revents = event_list[i].events; 27 28 ... 29 //該事件是一個讀事件而且是活躍的 30 if ((revents & EPOLLIN) && rev->active) { 31 32 ... 33 //如果設置了NGX_POST_EVENTS標識,事件放入到相應的隊列中 34 if (flags & NGX_POST_EVENTS) { 35 queue = (ngx_event_t **) (rev->accept ? 36 &ngx_posted_accept_events : &ngx_posted_events); 37 38 ngx_locked_post_event(rev, queue); 39 //否則立刻處理 40 } else { 41 rev->handler(rev); 42 } 43 } 44 45 //獲取寫事件 46 wev = c->write; 47 48 if ((revents & EPOLLOUT) && wev->active) { 49 50 ... 51 if (flags & NGX_POST_EVENTS) { 52 ngx_locked_post_event(wev, &ngx_posted_events); 53 54 } else { 55 wev->handler(wev); 56 } 57 } 58 } 59 ... 60 return NGX_OK; 61 }
ngx_process_events_and_timers流程
在Nginx中,每個worker進程都在ngx_worker_process_cycle方法中循環處理事件。
處理分發事件實際上就是調用的ngx_process_events_and_timers方法,循環調用該函數就是在處理所有的事件,該方法的核心的操作主要有以下3個:
1.調用所使用的事件驅動模塊實現的process_events方法,處理網絡事件(調用ngx_epoll_process_events方法)。
2.處理兩個post事件隊列中的事件(調用ngx_event_process_posted方法)。
3.處理定時器時間(調用ngx_event_expire_timers方法)
下圖展示了該方法的流程,結合前面的進行理解
事實上,在調用上面循環的方法之前ngx_event_core_module模塊會做一些初始化工作:
1.預分配cycle->connection數組充當連接池
2.預分配所有寫事件到cycle->read_events數組
3.預分配所有讀事件到cycle->write_events數組
(Nginx中連接池跟讀寫事件都是這個階段預先分配好的,其大小跟配置項有關)
4.為所有ngx_listening_t監聽對象中的connectin成員分配連接,同時對監聽端口的讀事件設置處理方法為ngx_event_accept(最后有這個方法的介紹)
5.將監聽對象連接的讀事件添加到事件驅動模塊中,這樣,epoll等事件模塊就開始檢測監聽服務,並開始向用戶提供服務了。
建立新連接
處理新連接事件的回調函數是ngx_event_accept,其原型如下
void ngx_event_accept(ngx_event_t *ev);
下圖展示了該方法的流程