nginx使用的文件异步io的基于linux内核的,而并不是glibc库提供的。glibc通过的异步io是基于多线程实现的,并不是真正意义上的异步io。nginx只是在读文件时使用了异步io。那么有的童鞋就会问:那nginx为什么在写文件时不使用异步io?原因是这样的:linux内核的文件异步io是不支持缓存操作的,而正常的写入文件时往往是写入内存中就立刻返回,这样效率就比较高,而使用异步io写入速度就会明显下降。
linux内核提供了5个系统调用完成文件操作的异步io,可以把这5个系统调用来相对epoll的系统调用来描述
io_setup epoll_create
io_setup会初始化异步io上下文(aio_context_t),这个描述符和epoll_create返回的描述符类似。
io_submit/io_cancel epoll_ctl
io_submit相当于向异步io添加事件,而io_cancel就是删除事件。这两个函数用到了iocb这个结构体:
struct iocb{
/*存储业务需要的指针,例如ngx_event_t事件的指针*/
u_int64_t aio_data;
/*操作码,取值范围是io_iocb_cmd_t中的枚举命令*/
u_int16_t aio_lio_opcode;
/*请求的优先级*/
int16_t aio_reqprio
/*文件描述符*/
u_int32_t aio_fildes;
/*读、写操作对应的用户态缓冲区*/
u_int64_t aio_buf;
/*文件中的偏移量*/
int_64_t aio_offset;
/*表示可以设置为IOCB_FLAG_RESFD,它会告诉内核当有异步io请求处理完成时使用eventfd进行通知*/
u_int32_t aio_flags;
/*表示当使用IOCB_FLAG_RESFD标志位时,用于进行事件通知的句柄*/
u_int32_t aio_resfd;
}
typedef enum io_iocb_cmd {
IO_CMD_PREAD = 0,
IO_CMD_PWRITE = 1,
IO_CMD_FSYNC = 2,
IO_CMD_FDSYNC = 3,
IO_CMD_POLL = 5,
IO_CMD_NOOP = 6,
} io_iocb_cmd_t;
struct io_event{
/*与提交事件时对应的iocb结构体的aio_data一致*/
uint_64_t data;
/*指向提交事件时对应的iocb*/
uint64_t obj;
/*异步io请求的机头体,res大于或等于0表示成功,小于0表示失败*/
int64_t res;
int64_t res2;
}
epoll模块会有几个全局变量:
#if (NGX_HAVE_FILE_AIO)
int ngx_eventfd = -1;
aio_context_t ngx_aio_ctx = 0;
static ngx_event_t ngx_eventfd_event;
static ngx_connection_t ngx_eventfd_conn;
#endif
只有在nginx使用了AIO的情况下才会编译进去。下面来看下ngx_epoll_init
static ngx_int_t
ngx_epoll_init(ngx_cycle_t *cycle, ngx_msec_t timer)
{
......
#if (NGX_HAVE_FILE_AIO)
ngx_epoll_aio_init(cycle, epcf);
#endif
......
}
在epoll初始化的时候就会对异步io进行初始化
static void
ngx_epoll_aio_init(ngx_cycle_t *cycle, ngx_epoll_conf_t *epcf)
{
......
ngx_eventfd = syscall(SYS_eventfd, 0);
......
if (ioctl(ngx_eventfd, FIONBIO, &n) == -1) {
ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
"ioctl(eventfd, FIONBIO) failed");
goto failed;
}
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;
}
......
ngx_eventfd_event.data = &ngx_eventfd_conn;
ngx_eventfd_event.handler = ngx_epoll_eventfd_handler;
ngx_eventfd_event.log = cycle->log;
ngx_eventfd_event.active = 1;
ngx_eventfd_conn.fd = ngx_eventfd;
ngx_eventfd_conn.read = &ngx_eventfd_event;
ngx_eventfd_conn.log = cycle->log;
ee.events = EPOLLIN|EPOLLET;
ee.data.ptr = &ngx_eventfd_conn;
if (epoll_ctl(ep, EPOLL_CTL_ADD, ngx_eventfd, &ee) != -1) {
return;
}
}
会调用io_setup初始化异步io的上下文,然后构造epoll使用的事件结构,并加入到epoll监听事件中。这样当某一个异步io事件完成后,ngx_eventfd就会出于可用状态。epoll_wait就会返回,并调用它的回调方法:ngx_epoll_eventfd_handler:
static void
ngx_epoll_eventfd_handler(ngx_event_t *ev)
{
......
n = read(ngx_eventfd, &ready, 8);
if (n != 8) {
......
while (ready) {
events = io_getevents(ngx_aio_ctx, 1, 64, event, &ts);
if (events > 0) {
ready -= events;
for (i = 0; i < events; i++) {
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_post_event(e, &ngx_posted_events);
......
}
讲述那么多,这里只是提到了异步io的初始化和通知,但是没有提到异步io事件的提交。异步io事件的提交是需要使用nginx的自己加入,如果某个模块需要使用异步io,就可以通过ngx_file_aio_read这个函数把异步io事件加入到通知队列中。
ssize_t
ngx_file_aio_read(ngx_file_t *file, u_char *buf, size_t size, off_t offset,
ngx_pool_t *pool)
{
......
if (!ngx_file_aio) {
return ngx_read_file(file, buf, size, offset);
}
aio = file->aio;
if (aio == NULL) {
aio = ngx_pcalloc(pool, sizeof(ngx_event_aio_t));
if (aio == NULL) {
return NGX_ERROR;
}
aio->file = file;
aio->fd = file->fd;
aio->event.data = aio;
aio->event.ready = 1;
aio->event.log = file->log;
#if (NGX_HAVE_AIO_SENDFILE)
aio->last_offset = -1;
#endif
file->aio = aio;
}
ev = &aio->event;
ngx_memzero(&aio->aiocb, sizeof(struct aiocb));
aio->aiocb.aio_fildes = file->fd;
aio->aiocb.aio_offset = offset;
aio->aiocb.aio_buf = buf;
aio->aiocb.aio_nbytes = size;
#if (NGX_HAVE_KQUEUE)
aio->aiocb.aio_sigevent.sigev_notify_kqueue = ngx_kqueue;
aio->aiocb.aio_sigevent.sigev_notify = SIGEV_KEVENT;
aio->aiocb.aio_sigevent.sigev_value.sigval_ptr = ev;
#endif
ev->handler = ngx_file_aio_event_handler;
n = aio_read(&aio->aiocb);
}
但是这里源码中只有aio_read并没有aio_submit,难道新版nginx里已经替换掉了?知道的童鞋告知一声,谢谢