### 概述
? ? ? 在前面的文章中《[Nginx 事件模塊](http://blog.csdn.net/chenhanzhun/article/details/42805757)》介紹了Nginx 的事件驅動框架以及不同類型事件驅動模塊的管理。本節基于前面的知識,簡單介紹下在Linux 系統下的 epoll 事件驅動模塊。關于 epoll 的使用與原理可以參照文章 《[epoll 解析](http://blog.csdn.net/chenhanzhun/article/details/42747127)》。在這里直接介紹Nginx 服務器基于事件驅動框架實現的epoll 事件驅動模塊。
### ngx_epoll_module 事件驅動模塊
### ngx_epoll_conf_t 結構體
? ? ?ngx_epoll_conf_t 結構體是保存ngx_epoll_module 事件驅動模塊的配置項結構。該結構體在文件[src/event/modules/ngx_epoll_module.c](http://lxr.nginx.org/source/src/event/modules/ngx_epoll_module.c) 中定義:
~~~
/* 存儲epoll模塊配置項結構體 */
typedef struct {
ngx_uint_t events; /* 表示epoll_wait函數返回的最大事件數 */
ngx_uint_t aio_requests; /* 并發處理異步IO事件個數 */
} ngx_epoll_conf_t;
~~~
### ngx_epoll_module 事件驅動模塊的定義
? ? ? 所有模塊的定義都是基于模塊通用接口 ngx_module_t 結構,ngx_epoll_module 模塊在文件[src/event/modules/ngx_epoll_module.c](http://lxr.nginx.org/source/src/event/modules/ngx_epoll_module.c) 中定義如下:
~~~
/* epoll模塊定義 */
ngx_module_t ngx_epoll_module = {
NGX_MODULE_V1,
&ngx_epoll_module_ctx, /* module context */
ngx_epoll_commands, /* module directives */
NGX_EVENT_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
};
~~~
? ? ? 在 ngx_epoll_module 模塊的定義中,其中定義了該模塊感興趣的配置項ngx_epoll_commands 數組,該配置項數組在文件[src/event/modules/ngx_epoll_module.c](http://lxr.nginx.org/source/src/event/modules/ngx_epoll_module.c) 中定義:
~~~
/* 定義epoll模塊感興趣的配置項結構數組 */
static ngx_command_t ngx_epoll_commands[] = {
/*
* epoll_events配置項表示epoll_wait函數每次返回的最多事件數(即第3個參數),
* 在ngx_epoll_init函數中會預分配epoll_events配置項指定的epoll_event結構體;
*/
{ ngx_string("epoll_events"),
NGX_EVENT_CONF|NGX_CONF_TAKE1,
ngx_conf_set_num_slot,
0,
offsetof(ngx_epoll_conf_t, events),
NULL },
/*
* 該配置項表示創建的異步IO上下文能并發處理異步IO事件的個數,
* 即io_setup函數的第一個參數;
*/
{ ngx_string("worker_aio_requests"),
NGX_EVENT_CONF|NGX_CONF_TAKE1,
ngx_conf_set_num_slot,
0,
offsetof(ngx_epoll_conf_t, aio_requests),
NULL },
ngx_null_command
};
~~~
? ? ? 在 ngx_epoll_module 模塊的定義中,定義了該模塊的上下文結構ngx_epoll_module_ctx,該上下文結構是基于事件模塊的通用接口ngx_event_module_t 結構來定義的。該上下文結構在文件[src/event/modules/ngx_epoll_module.c](http://lxr.nginx.org/source/src/event/modules/ngx_epoll_module.c) 中定義:
~~~
/* 由事件模塊通用接口ngx_event_module_t定義的epoll模塊上下文結構 */
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 */
}
};
~~~
? ? ? 在 ngx_epoll_module 模塊的上下文事件接口結構中,重點定義了ngx_event_actions_t 結構中的接口回調方法。
### ngx_epoll_module 事件驅動模塊的操作
? ? ? ngx_epoll_module 模塊的操作由ngx_epoll_module 模塊的上下文事件接口結構中成員actions 實現。該成員實現的方法如下所示:
~~~
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 */
~~~
#### ngx_epoll_module 模塊的初始化
? ? ? ngx_epoll_module 模塊的初始化由函數ngx_epoll_init 實現。該函數主要做了兩件事:創建epoll 對象 和 創建 event_list 數組(調用epoll_wait 函數時用于存儲從內核復制的已就緒的事件);該函數在文件[src/event/modules/ngx_epoll_module.c](http://lxr.nginx.org/source/src/event/modules/ngx_epoll_module.c) 中定義:
~~~
static int ep = -1; /* epoll對象描述符 */
static struct epoll_event *event_list; /* 作為epoll_wait函數的第二個參數,保存從內存復制的事件 */
static ngx_uint_t nevents; /* epoll_wait函數返回的最多事件數 */
/* epoll模塊初始化函數 */
static ngx_int_t
ngx_epoll_init(ngx_cycle_t *cycle, ngx_msec_t timer)
{
ngx_epoll_conf_t *epcf;
/* 獲取ngx_epoll_module模塊的配置項結構 */
epcf = ngx_event_get_conf(cycle->conf_ctx, ngx_epoll_module);
if (ep == -1) {
/* 調用epoll_create函數創建epoll對象描述符 */
ep = epoll_create(cycle->connection_n / 2);
/* 若創建失敗,則出錯返回 */
if (ep == -1) {
ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
"epoll_create() failed");
return NGX_ERROR;
}
#if (NGX_HAVE_FILE_AIO)
/* 若系統支持異步IO,則初始化異步IO */
ngx_epoll_aio_init(cycle, epcf);
#endif
}
/*
* 預分配events個epoll_event結構event_list,event_list是存儲產生事件的數組;
* events由epoll_events配置項指定;
*/
if (nevents < epcf->events) {
/*
* 若現有event_list個數小于配置項所指定的值epcf->events,
* 則先釋放,再從新分配;
*/
if (event_list) {
ngx_free(event_list);
}
/* 預分配epcf->events個epoll_event結構,并使event_list指向該地址 */
event_list = ngx_alloc(sizeof(struct epoll_event) * epcf->events,
cycle->log);
if (event_list == NULL) {
return NGX_ERROR;
}
}
/* 設置正確的epoll_event結構個數 */
nevents = epcf->events;
/* 指定IO的讀寫方法 */
/*
* 初始化全局變量ngx_io, ngx_os_io定義為:
ngx_os_io_t ngx_os_io = {
ngx_unix_recv,
ngx_readv_chain,
ngx_udp_unix_recv,
ngx_unix_send,
ngx_writev_chain,
0
};(位于src/os/unix/ngx_posix_init.c)
*/
ngx_io = ngx_os_io;
/* 設置ngx_event_actions 接口 */
ngx_event_actions = ngx_epoll_module_ctx.actions;
#if (NGX_HAVE_CLEAR_EVENT)
/* ET模式 */
ngx_event_flags = NGX_USE_CLEAR_EVENT
#else
/* LT模式 */
ngx_event_flags = NGX_USE_LEVEL_EVENT
#endif
|NGX_USE_GREEDY_EVENT
|NGX_USE_EPOLL_EVENT;
return NGX_OK;
}
~~~
#### ngx_epoll_module 模塊的事件處理
? ? ? ngx_epoll_module 模塊的事件處理由函數ngx_epoll_process_events 實現。ngx_epoll_process_events 函數是實現事件收集、事件發送的接口。該函數在文件[src/event/modules/ngx_epoll_module.c](http://lxr.nginx.org/source/src/event/modules/ngx_epoll_module.c) 中定義:
~~~
/* 處理已準備就緒的事件 */
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在規定的timer時間內等待監控的事件準備就緒 */
events = epoll_wait(ep, event_list, (int) nevents, timer);
/* 若出錯,設置錯誤編碼 */
err = (events == -1) ? ngx_errno : 0;
/*
* 若沒有設置timer_resolution配置項時,
* NGX_UPDATE_TIME 標志表示每次調用epoll_wait函數返回后需要更新時間;
* 若設置timer_resolution配置項,
* 則每隔timer_resolution配置項參數會設置ngx_event_timer_alarm為1,表示需要更新時間;
*/
if (flags & NGX_UPDATE_TIME || ngx_event_timer_alarm) {
/* 更新時間,將時間緩存到一組全局變量中,方便程序高效獲取事件 */
ngx_time_update();
}
/* 處理epoll_wait的錯誤 */
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;
}
/*
* 若epoll_wait返回的事件數events為0,則有兩種可能:
* 1、超時返回,即時間超過timer;
* 2、在限定的timer時間內返回,此時表示出錯error返回;
*/
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);
/* 遍歷由epoll_wait返回的所有已準備就緒的事件,并處理這些事件 */
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);
/* 獲取讀事件 */
rev = c->read;
/*
* 同一連接的讀寫事件的instance標志位是相同的;
* 若fd描述符為-1,或連接對象讀事件的instance標志位不相同,則判為過期事件;
*/
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 = 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);
/* 記錄epoll_wait的錯誤返回狀態 */
/*
* EPOLLERR表示連接出錯;EPOLLHUP表示收到RST報文;
* 檢測到上面這兩種錯誤時,TCP連接中可能存在未讀取的數據;
*/
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 0
if (revents & ~(EPOLLIN|EPOLLOUT|EPOLLERR|EPOLLHUP)) {
ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,
"strange epoll_wait() events fd:%d ev:%04XD",
c->fd, revents);
}
#endif
/*
* 若連接發生錯誤且未設置EPOLLIN、EPOLLOUT,
* 則將EPOLLIN、EPOLLOUT添加到revents中;
* 即在調用讀寫事件時能夠處理連接的錯誤;
*/
if ((revents & (EPOLLERR|EPOLLHUP))
&& (revents & (EPOLLIN|EPOLLOUT)) == 0)
{
/*
* if the error events were returned without EPOLLIN or EPOLLOUT,
* then add these flags to handle the events at least in one
* active handler
*/
revents |= EPOLLIN|EPOLLOUT;
}
/* 連接有可讀事件,且該讀事件是active活躍的 */
if ((revents & EPOLLIN) && rev->active) {
#if (NGX_HAVE_EPOLLRDHUP)
/* EPOLLRDHUP表示連接對端關閉了讀取端 */
if (revents & EPOLLRDHUP) {
rev->pending_eof = 1;
}
#endif
if ((flags & NGX_POST_THREAD_EVENTS) && !rev->accept) {
rev->posted_ready = 1;
} else {
/* 讀事件已準備就緒 */
/*
* 這里要區分active與ready:
* active是指事件被添加到epoll對象的監控中,
* 而ready表示被監控的事件已經準備就緒,即可以對其進程IO處理;
*/
rev->ready = 1;
}
/*
* NGX_POST_EVENTS表示已準備就緒的事件需要延遲處理,
* 根據accept標志位將事件加入到相應的隊列中;
*/
if (flags & NGX_POST_EVENTS) {
queue = (ngx_event_t **) (rev->accept ?
&ngx_posted_accept_events : &ngx_posted_events);
ngx_locked_post_event(rev, queue);
} else {
/* 若不延遲處理,則直接調用事件的處理函數 */
rev->handler(rev);
}
}
/* 獲取連接的寫事件,寫事件的處理邏輯過程與讀事件類似 */
wev = c->write;
/* 連接有可寫事件,且該寫事件是active活躍的 */
if ((revents & EPOLLOUT) && wev->active) {
/* 檢查寫事件是否過期 */
if (c->fd == -1 || wev->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;
}
if (flags & NGX_POST_THREAD_EVENTS) {
wev->posted_ready = 1;
} else {
/* 寫事件已準備就緒 */
wev->ready = 1;
}
/*
* NGX_POST_EVENTS表示已準備就緒的事件需要延遲處理,
* 根據accept標志位將事件加入到相應的隊列中;
*/
if (flags & NGX_POST_EVENTS) {
ngx_locked_post_event(wev, &ngx_posted_events);
} else {
/* 若不延遲處理,則直接調用事件的處理函數 */
wev->handler(wev);
}
}
}
ngx_mutex_unlock(ngx_posted_events_mutex);
return NGX_OK;
}
~~~
#### ngx_epoll_module 模塊的事件添加與刪除
? ? ? ngx_epoll_module 模塊的事件添加與刪除分別由函數ngx_epoll_add_event 與ngx_epoll_del_event 實現。這兩個函數的實現都是通過調用epoll_ctl 函數。具體實現在文件 [src/event/modules/ngx_epoll_module.c](http://lxr.nginx.org/source/src/event/modules/ngx_epoll_module.c) 中定義:
~~~
/* 將某個描述符的某個事件添加到epoll對象的監控機制中 */
static ngx_int_t
ngx_epoll_add_event(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags)
{
int op;
uint32_t events, prev;
ngx_event_t *e;
ngx_connection_t *c;
struct epoll_event ee;
/* 每個事件的data成員都存放著其對應的ngx_connection_t連接 */
/* 獲取事件關聯的連接 */
c = ev->data;
/* events參數是方便下面確定當前事件是可讀還是可寫 */
events = (uint32_t) event;
/*
* 這里在判斷事件類型是可讀還是可寫,必須根據事件的active標志位來判斷事件是否活躍;
* 因為epoll_ctl函數有添加add和修改mod模式,
* 若一個事件所關聯的連接已經在epoll對象的監控中,則只需修改事件的類型即可;
* 若一個事件所關聯的連接沒有在epoll對象的監控中,則需要將其相應的事件類型注冊到epoll對象中;
* 這樣做的情況是避免與事件相關聯的連接兩次注冊到epoll對象中;
*/
if (event == NGX_READ_EVENT) {
/*
* 若待添加的事件類型event是可讀;
* 則首先判斷該事件所關聯的連接是否將寫事件添加到epoll對象中,
* 即先判斷關聯的連接的寫事件是否為活躍事件;
*/
e = c->write;
prev = EPOLLOUT;
#if (NGX_READ_EVENT != EPOLLIN|EPOLLRDHUP)
events = EPOLLIN|EPOLLRDHUP;
#endif
} else {
e = c->read;
prev = EPOLLIN|EPOLLRDHUP;
#if (NGX_WRITE_EVENT != EPOLLOUT)
events = EPOLLOUT;
#endif
}
/* 根據active標志位確定事件是否為活躍事件,以決定到達是修改還是添加事件 */
if (e->active) {
/* 若當前事件是活躍事件,則只需修改其事件類型即可 */
op = EPOLL_CTL_MOD;
events |= prev;
} else {
/* 若當前事件不是活躍事件,則將該事件添加到epoll對象中 */
op = EPOLL_CTL_ADD;
}
/* 將flags參數加入到events標志位中 */
ee.events = events | (uint32_t) flags;
/* prt存儲事件關聯的連接對象ngx_connection_t以及過期事件instance標志位 */
ee.data.ptr = (void *) ((uintptr_t) c | ev->instance);
ngx_log_debug3(NGX_LOG_DEBUG_EVENT, ev->log, 0,
"epoll add event: fd:%d op:%d ev:%08XD",
c->fd, op, ee.events);
/* 調用epoll_ctl方法向epoll對象添加事件或在epoll對象中修改事件 */
if (epoll_ctl(ep, op, c->fd, &ee) == -1) {
ngx_log_error(NGX_LOG_ALERT, ev->log, ngx_errno,
"epoll_ctl(%d, %d) failed", op, c->fd);
return NGX_ERROR;
}
/* 將該事件的active標志位設置為1,表示當前事件是活躍事件 */
ev->active = 1;
#if 0
ev->oneshot = (flags & NGX_ONESHOT_EVENT) ? 1 : 0;
#endif
return NGX_OK;
}
/* 將某個連接的某個事件從epoll對象監控中刪除 */
static ngx_int_t
ngx_epoll_del_event(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags)
{
int op;
uint32_t prev;
ngx_event_t *e;
ngx_connection_t *c;
struct epoll_event ee;
/*
* when the file descriptor is closed, the epoll automatically deletes
* it from its queue, so we do not need to delete explicitly the event
* before the closing the file descriptor
*/
/* 當事件關聯的文件描述符關閉后,epoll對象自動將其事件刪除 */
if (flags & NGX_CLOSE_EVENT) {
ev->active = 0;
return NGX_OK;
}
/* 獲取事件關聯的連接對象 */
c = ev->data;
/* 根據event參數判斷當前刪除的是讀事件還是寫事件 */
if (event == NGX_READ_EVENT) {
/* 若要刪除讀事件,則首先判斷寫事件的active標志位 */
e = c->write;
prev = EPOLLOUT;
} else {
/* 若要刪除寫事件,則判斷讀事件的active標志位 */
e = c->read;
prev = EPOLLIN|EPOLLRDHUP;
}
/*
* 若要刪除讀事件,且寫事件是活躍事件,則修改事件類型即可;
* 若要刪除寫事件,且讀事件是活躍事件,則修改事件類型即可;
*/
if (e->active) {
op = EPOLL_CTL_MOD;
ee.events = prev | (uint32_t) flags;
ee.data.ptr = (void *) ((uintptr_t) c | ev->instance);
} else {
/* 若讀寫事件都不是活躍事件,此時表示事件未準備就緒,則將其刪除 */
op = EPOLL_CTL_DEL;
ee.events = 0;
ee.data.ptr = NULL;
}
ngx_log_debug3(NGX_LOG_DEBUG_EVENT, ev->log, 0,
"epoll del event: fd:%d op:%d ev:%08XD",
c->fd, op, ee.events);
/* 刪除或修改事件 */
if (epoll_ctl(ep, op, c->fd, &ee) == -1) {
ngx_log_error(NGX_LOG_ALERT, ev->log, ngx_errno,
"epoll_ctl(%d, %d) failed", op, c->fd);
return NGX_ERROR;
}
/* 設置當前事件的active標志位 */
ev->active = 0;
return NGX_OK;
}
~~~
#### ngx_epoll_module 模塊的連接添加與刪除
? ? ? ngx_epoll_module 模塊的連接添加與刪除分別由函數ngx_epoll_add_connection 與ngx_epoll_del_connection 實現。這兩個函數的實現都是通過調用epoll_ctl 函數。具體實現在文件 [src/event/modules/ngx_epoll_module.c](http://lxr.nginx.org/source/src/event/modules/ngx_epoll_module.c) 中定義:
~~~
/* 將指定連接所關聯的描述符添加到epoll對象中 */
static ngx_int_t
ngx_epoll_add_connection(ngx_connection_t *c)
{
struct epoll_event ee;
/* 設置事件的類型:可讀、可寫、ET模式 */
ee.events = EPOLLIN|EPOLLOUT|EPOLLET|EPOLLRDHUP;
ee.data.ptr = (void *) ((uintptr_t) c | c->read->instance);
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
"epoll add connection: fd:%d ev:%08XD", c->fd, ee.events);
/* 調用epoll_ctl方法將連接所關聯的描述符添加到epoll對象中 */
if (epoll_ctl(ep, EPOLL_CTL_ADD, c->fd, &ee) == -1) {
ngx_log_error(NGX_LOG_ALERT, c->log, ngx_errno,
"epoll_ctl(EPOLL_CTL_ADD, %d) failed", c->fd);
return NGX_ERROR;
}
/* 設置讀寫事件的active標志位 */
c->read->active = 1;
c->write->active = 1;
return NGX_OK;
}
Nginx
/* 將連接所關聯的描述符從epoll對象中刪除 */
static ngx_int_t
ngx_epoll_del_connection(ngx_connection_t *c, ngx_uint_t flags)
{
int op;
struct epoll_event ee;
/*
* when the file descriptor is closed the epoll automatically deletes
* it from its queue so we do not need to delete explicitly the event
* before the closing the file descriptor
*/
if (flags & NGX_CLOSE_EVENT) {
c->read->active = 0;
c->write->active = 0;
return NGX_OK;
}
ngx_log_debug1(NGX_LOG_DEBUG_EVENTNginx, c->log, 0,
"epoll del connection: fd:%d", c->fd);
op = EPOLL_CTL_DEL;
ee.events = 0;
ee.data.ptr = NULL;
/* 調用epoll_ctl方法將描述符從epoll對象中刪除 */
if (epoll_ctl(ep, op, c->fd, &ee) == -1) {
ngx_log_error(NGX_LOG_ALERT, c->log, ngx_errno,
"epoll_ctl(%d, %d) failed", op, c->fd);
return NGX_ERROR;
}
/* 設置描述符讀寫事件的active標志位 */
c->read->active = 0;
c->write->active = 0;
return NGX_OK;
}
~~~
### ngx_epoll_module 模塊的異步 I/O
? ? ? 在 Nginx 中,文件異步 I/O 事件完成后的通知是集成到 epoll 對象中。該模塊的文件異步I/O 實現如下:
~~~
#if (NGX_HAVE_FILE_AIO)
int ngx_eventfd = -1; /* 用于通知異步IO的事件描述符 */
aio_context_t ngx_aio_ctx = 0; /* 異步IO的上下文結構,由io_setup 函數初始化 */
static ngx_event_t ngx_eventfd_event; /* 異步IO事件 */
static ngx_connection_t ngx_eventfd_conn; /* 異步IO事件所對應的連接ngx_connection_t */
#endif
#if (NGX_HAVE_FILE_AIO)
/*
* We call io_setup(), io_destroy() io_submit(), and io_getevents() directly
* as syscalls instead of libaio usage, because the library header file
* supports eventfd() since 0.3.107 version only.
*
* Also we do not use eventfd() in glibc, because glibc supports it
* since 2.8 version and glibc maps two syscalls eventfd() and eventfd2()
* into single eventfd() function with different number of parameters.
*/
/* 初始化文件異步IO的上下文結構 */
static int
io_setup(u_int nr_reqs, aio_context_t *ctx)
{
return syscall(SYS_io_setup, nr_reqs, ctx);
}
/* 銷毀文件異步IO的上下文結構 */
static int
io_destroy(aio_context_t ctx)
{
return syscall(SYS_io_destroy, ctx);
}
/* 從文件異步IO操作隊列中讀取操作 */
static int
io_getevents(aio_context_t ctx, long min_nr, long nr, struct io_event *events,
struct timespec *tmo)
{
return syscall(SYS_io_getevents, ctx, min_nr, nr, events, tmo);
}
/* 異步IO的初始化 */
static void
ngx_epoll_aio_init(ngx_cycle_t *cycle, ngx_epoll_conf_t *epcf)
{
int n;
struct epoll_event ee;
/* 使用Linux系統調用獲取一個描述符句柄 */
ngx_eventfd = syscall(SYS_eventfd, 0);
if (ngx_eventfd == -1) {
ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
"eventfd() failed");
ngx_file_aio = 0;
return;
}
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
"eventfd: %d", ngx_eventfd);
n = 1;
/* 設置ngx_eventfd描述符句柄為非阻塞IO模式 */
if (ioctl(ngx_eventfd, FIONBIO, &n) == -1) {
ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
"ioctl(eventfd, FIONBIO) failed");
goto failed;
}
/* 初始化文件異步IO的上下文結構 */
if (io_setup(epcf->aio_requests, &ngx_aio_ctx) == -1) {
ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
"io_setup() failed");
goto failed;
}
/* 設置異步IO事件ngx_eventfd_event,該事件是ngx_eventfd對應的ngx_event事件 */
/* 用于異步IO完成通知的ngx_eventfd_event事件,它與ngx_eventfd_conn連接對應 */
ngx_eventfd_event.data = &ngx_eventfd_conn;
/* 在異步IO事件完成后,調用ngx_epoll_eventfd_handler處理方法 */
ngx_eventfd_event.handler = ngx_epoll_eventfd_handler;
/* 設置事件相應的日志 */
ngx_eventfd_event.log = cycle->log;
/* 設置active標志位 */
ngx_eventfd_event.active = 1;
/* 初始化ngx_eventfd_conn 連接 */
ngx_eventfd_conn.fd = ngx_eventfd;
/* ngx_eventfd_conn連接的讀事件就是ngx_eventfd_event事件 */
ngx_eventfd_conn.read = &ngx_eventfd_event;
/* 設置連接的相應日志 */
ngx_eventfd_conn.log = cycle->log;
ee.events = EPOLLIN|EPOLLET;
ee.data.ptr = &ngx_eventfd_conn;
/* 向epoll對象添加異步IO通知描述符ngx_eventfd */
if (epoll_ctl(ep, EPOLL_CTL_ADD, ngx_eventfd, &ee) != -1) {
return;
}
/* 若添加出錯,則銷毀文件異步IO上下文結構,并返回 */
ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
"epoll_ctl(EPOLL_CTL_ADD, eventfd) failed");
if (io_destroy(ngx_aio_ctx) == -1) {
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
"io_destroy() failed");
}
failed:
if (close(ngx_eventfd) == -1) {
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
"eventfd close() failed");
}
ngx_eventfd = -1;
ngx_aio_ctx = 0;
ngx_file_aio = 0;
}
#endif
#if (NGX_HAVE_FILE_AIO)
/* 處理已完成的異步IO事件 */
static void
ngx_epoll_eventfd_handler(ngx_event_t *ev)
{
int n, events;
long i;
uint64_t ready;
ngx_err_t err;
ngx_event_t *e;
ngx_event_aio_t *aio;
struct io_event event[64]; /* 一次最多處理64個事件 */
struct timespec ts;
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, 0, "eventfd handler");
/* 獲取已完成的事件數,并將其設置到ready */
n = read(ngx_eventfd, &ready, 8);
err = ngx_errno;
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, ev->log, 0, "eventfd: %d", n);
if (n != 8) {
if (n == -1) {
if (err == NGX_EAGAIN) {
return;
}
ngx_log_error(NGX_LOG_ALERT, ev->log, err, "read(eventfd) failed");
return;
}
ngx_log_error(NGX_LOG_ALERT, ev->log, 0,
"read(eventfd) returned only %d bytes", n);
return;
}
ts.tv_sec = 0;
ts.tv_nsec = 0;
/* 遍歷ready,處理異步IO事件 */
while (ready) {
/* 獲取已完成的異步IO事件 */
events = io_getevents(ngx_aio_ctx, 1, 64, event, &ts);
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, ev->log, 0,
"io_getevents: %l", events);
if (events > 0) {
ready -= events;/* ready減去已經取出的事件數 */
/* 處理已被取出的事件 */
for (i = 0; i < events; i++) {
ngx_log_debug4(NGX_LOG_DEBUG_EVENT, ev->log, 0,
"io_event: %uXL %uXL %L %L",
event[i].data, event[i].obj,
event[i].res, event[i].res2);
/* 獲取異步IO事件對應的實際事件 */
e = (ngx_event_t *) (uintptr_t) event[i].data;
e->complete = 1;
e->active = 0;
e->ready = 1;
aio = e->data;
aio->res = event[i].res;
/* 將實際事件加入到ngx_posted_event隊列中等待處理 */
ngx_post_event(e, &ngx_posted_events);
}
continue;
}
if (events == 0) {
return;
}
/* events == -1 */
ngx_log_error(NGX_LOG_ALERT, ev->log, ngx_errno,
"io_getevents() failed");
return;
}
}
#endif
~~~
參考資料:
《深入理解Nginx》
《[模塊ngx_epoll_module詳解](http://blog.csdn.net/xiajun07061225/article/details/9250341)》
《 [nginx epoll詳解](http://blog.csdn.net/freeinfor/article/details/17008131)》
《[Nginx源碼分析-Epoll模塊](http://www.alidata.org/archives/1296)》
《[關于ngx_epoll_add_event的一些解釋](http://blog.csdn.net/brainkick/article/details/9080789)》
- 前言
- Nginx 配置文件
- Nginx 內存池管理
- Nginx 基本數據結構
- Nginx 數組結構 ngx_array_t
- Nginx 鏈表結構 ngx_list_t
- Nginx 隊列雙向鏈表結構 ngx_queue_t
- Nginx 哈希表結構 ngx_hash_t
- Nginx 紅黑樹結構 ngx_rbtree_t
- Nginx 模塊開發
- Nginx 啟動初始化過程
- Nginx 配置解析
- Nginx 中的 upstream 與 subrequest 機制
- Nginx 源碼結構分析
- Nginx 事件模塊
- Nginx 的 epoll 事件驅動模塊
- Nginx 定時器事件
- Nginx 事件驅動模塊連接處理
- Nginx 中 HTTP 模塊初始化
- Nginx 中處理 HTTP 請求
- Nginx 中 upstream 機制的實現
- Nginx 中 upstream 機制的負載均衡