9.9.2 ngx_epoll_module模块中实现的针对文件的异步I/O

在Nginx中,文件异步I/O事件完成后的通知是集成到epoll中的,它是通过9.9.1节中介绍的IOCB_FLAG_RESFD标志位完成的。下面看看文件异步I/O事件在ngx_epoll_module模块中是如何实现的,其中在文件异步I/O机制中定义的全局变量如下。


//用于通知异步I/O事件的描述符,它与iocb结构体中的aio_resfd成员是一致的

int ngx_eventfd=-1;

//异步I/O的上下文,全局唯一,必须经过io_setup初始化才能使用

aio_context_t ngx_aio_ctx=0;

/异步I/O事件完成后进行通知的描述符,也就是ngx_eventfd所对应的ngx_event_t事件/

static ngx_event_t ngx_eventfd_event;

/异步I/O事件完成后进行通知的描述符ngx_eventfd所对应的ngx_connection_t连接/

static ngx_connection_t ngx_eventfd_conn;


在9.6.3节的ngx_epoll_init代码中,在epoll_create执行完成后如果开启了文件异步I/O功能,则会调用ngx_epoll_aio_init方法。现在详细描述一下ngx_epoll_aio_init方法中做了些什么,如下所示。


define SYS_eventfd 323

static void ngx_epoll_aio_init(ngx_cycle_tcycle,ngx_epoll_conf_tepcf)

{

int n;

struct epoll_event ee;

//使用Linux中第323个系统调用获取一个描述符句柄

ngx_eventfd=syscall(SYS_eventfd,0);

……

//设置ngx_eventfd为无阻塞

if(ioctl(ngx_eventfd,FIONBIO,&n)==-1){

……

}

//初始化文件异步I/O的上下文

if(io_setup(epcf->aio_requests,&ngx_aio_ctx)==-1){

……

}

/设置用于异步I/O完成通知的ngx_eventfd_event事件,它与ngx_eventfd_conn连接是对应的/

ngx_eventfd_event.data=&ngx_eventfd_conn;

//在异步I/O事件完成后,使用ngx_epoll_eventfd_handler方法处理

ngx_eventfd_event.handler=ngx_epoll_eventfd_handler;

ngx_eventfd_event.log=cycle->log;

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中添加到异步I/O的通知描述符ngx_eventfd

if(epoll_ctl(ep,EPOLL_CTL_ADD,ngx_eventfd,&ee)!=-1){

return;

}

……

}


这样,ngx_epoll_aio_init方法会把异步I/O与epoll结合起来,当某一个异步I/O事件完成后,ngx_eventfd句柄就处于可用状态,这样epoll_wait在返回ngx_eventfd_event事件后就会调用它的回调方法ngx_epoll_eventfd_handler处理已经完成的异步I/O事件,下面看一下ngx_epoll_eventfd_handler方法主要在做些什么,代码如下所示。


static void ngx_epoll_eventfd_handler(ngx_event_t*ev)

{

int n,events;

uint64_t ready;

ngx_event_t*e;

//一次性最多处理64个事件

struct io_event event[64];

struct timespec ts;

/获取已经完成的事件数目,并设置到ready中,注意,这个ready是可以大于64的/

n=read(ngx_eventfd,&ready,8);

……

//ready表示还未处理的事件。当ready大于0时继续处理

while(ready){

//调用io_getevents获取已经完成的异步I/O事件

events=io_getevents(ngx_aio_ctx,1,64,event,&ts);

if(events>0){

//将ready减去已经取出的事件

ready-=events;

//处理event数组里的事件

for(i=0;i<events;i++){

//data成员指向这个异步I/O事件对应着的实际事件

e=(ngx_event_t*)(uintptr_t)event[i].data;

……

//将该事件放到ngx_posted_events队列中延后执行

ngx_post_event(e,&ngx_posted_events);

}

continue;

}

if(events==0){

return;

}

return;

}

}


整个网络事件的驱动机制就是这样通过ngx_eventfd通知描述符和ngx_epoll_eventfd_handler回调方法,并与文件异步I/O事件结合起来的。

那么,怎样向异步I/O上下文中提交异步I/O操作呢?看看ngx_linux_aio_read.c文件中的ngx_file_aio_read方法,在打开文件异步I/O后,这个方法将会负责磁盘文件的读取,如下所示。


ssize_t ngx_file_aio_read(ngx_file_tfile,u_charbuf,size_t size,off_t offset,ngx_pool_t*pool)

{

ngx_err_t err;

struct iocb*piocb[1];

ngx_event_t*ev;

ngx_event_aio_t*aio;

……

aio=file->aio;

ev=&aio->event;

……

ngx_memzero(&aio->aiocb,sizeof(struct iocb));

/设置9.9.1节中介绍过的iocb结构体,这里的aiocb成员就是iocb类型。注意,aio_data已经设置为这个ngx_event_t事件的指针,这样,从io_getevents方法获取的io_event对象中的data也是这个指针/

aio->aiocb.aio_data=(uint64_t)(uintptr_t)ev;

aio->aiocb.aio_lio_opcode=IOCB_CMD_PREAD;

aio->aiocb.aio_fildes=file->fd;

aio->aiocb.aio_buf=(uint64_t)(uintptr_t)buf;

aio->aiocb.aio_nbytes=size;

aio->aiocb.aio_offset=offset;

aio->aiocb.aio_flags=IOCB_FLAG_RESFD;

aio->aiocb.aio_resfd=ngx_eventfd;

/*设置事件的回调方法为ngx_file_aio_event_handler,它的调用关系类似这样:epoll_wait中

调用ngx_epoll_eventfd_handler方法将当前事件放入到ngx_posted_events队列中,在延后执行的队列中

调用ngx_file_aio_event_handler方法*/

ev->handler=ngx_file_aio_event_handler;

piocb[0]=&aio->aiocb;

/调用io_submit向ngx_aio_ctx异步I/O上下文中添加1个事件,返回1表示成功/

if(io_submit(ngx_aio_ctx,1,piocb)==1){

ev->active=1;

ev->ready=0;

ev->complete=0;

return NGX_AGAIN;

}

……

}


下面看一下ngx_event_aio_t结构体的定义。


typedef struct ngx_event_aio_s ngx_event_aio_t;

struct ngx_event_aio_s{

void*data;

//这是真正由业务模块实现的方法,在异步I/O事件完成后被调用

ngx_event_handler_pt handler;

ngx_file_t*file;

ngx_fd_t fd;

if(NGX_HAVE_EVENTFD)

int64_t res;

else

ngx_err_t err;

size_t nbytes;

endif

if(NGX_HAVE_AIO_SENDFILE)

off_t last_offset;

endif

//这里的ngx_aiocb_t就是9.9.1节中介绍的iocb结构体

ngx_aiocb_t aiocb;

ngx_event_t event;

};


这样,ngx_file_aio_read方法会向异步I/O上下文中添加事件,该epoll_wait在通过ngx_eventfd描述符检测到异步I/O事件后,会再调用ngx_epoll_eventfd_handler方法将io_event事件取出来,放入ngx_posted_events队列中延后执行。ngx_posted_events队列中的事件执行时,则会调用ngx_file_aio_event_handler方法。下面看一下ngx_file_aio_event_handler方法做了些什么,代码如下所示。


static void ngx_file_aio_event_handler(ngx_event_t*ev)

{

ngx_event_aio_t*aio;

aio=ev->data;

aio->handler(ev);

}


这里调用了ngx_event_aio_t结构体的handler回调方法,这个回调方法是由真正的业务模块实现的,也就是说,任一个业务模块想使用文件异步I/O,就可以实现handler方法,这样,在文件异步操作完成后,该方法就会被回调。