nginx的文件异步io

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;


io_getevents相当于epoll_wait,这里用到了io_event:

struct io_event{
       /*与提交事件时对应的iocb结构体的aio_data一致*/
       uint_64_t data;
       /*指向提交事件时对应的iocb*/
       uint64_t   obj;
      /*异步io请求的机头体,res大于或等于0表示成功,小于0表示失败*/
       int64_t     res;
       int64_t     res2;
}

ngx_epoll_module模块实现针对文件的异步io:

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);
......
}

read调用是回去已经完成的事件数目,并设置到ready中,接着就调用io_getevents获取完成的异步io,只要ready大于0就会继续处理。

讲述那么多,这里只是提到了异步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里已经替换掉了?知道的童鞋告知一声,谢谢





你可能感兴趣的:(nginx源码分析)