Nginx基础. eventfd, 异步IO 与epoll的相互协作



摘自: http://blog.csdn.net/u012062760/article/details/48732555


关于eventfd.
对于eventfd, 这里只是简单的讲它的功能. 看manpage就足够了, 其中的例子也很容易看懂
eventfd函数可以创建一个efd描述符, 该描述符在内核中维护着一个计数器counter.
在调用eventfd时, 可以传入参数指定内核中维护着的计数器的值.
如果这样调用:

  1. int efd = eventfd(0, 0);  
      int efd = eventfd(0, 0);
那么计数器值为0.
如果此时调用:
  1. read(efd, &ret, sizeof(ret));   
      read(efd, &ret, sizeof(ret)); 
那么会休眠. 直到有人调用:
  1. int add = 1;  
  2. write(efd, &add, sizeof(add))  
      int add = 1;
      write(efd, &add, sizeof(add))
这时候上面的read才会停止休眠, 得到的ret值为1. 如果write写入的add值不是1, 而是5
那么read函数得到的ret值也会是5. read之后, efd对应的内核中的计数器值就回到了0, 此时再次调用:
  1. read(efd, &ret, sizeof(ret));   
      read(efd, &ret, sizeof(ret)); 
那么进程又会阻塞. 除非, efd被你设置成了非阻塞... 会返回EAGAIN错误.



关于异步IO
这里将认识异步IO, 以及epoll是如何与异步IO相互协作的.
在linux下, 有两种异步IO. 一种是由glibc实现的aio, 它是直接在用户空间用pthread进行模拟的伪异步IO; 还有一种是内核实现的aio. 相关的系统调用是以 io_xxx 形式出现的.
在nginx中, 采用的是内核实现的aio, 只有在内核中成功完成了磁盘操作, 内核才会通知进程, 进而使磁盘文件的处理与网络事件的处理同样高效.
内核提供的aio的优点是, 能够同时提交多个io请求给内核, 当大量事件堆积到IO设备的队列中时, 内核将发挥出io调度算法的优势, 对到来的事件处理进行优化, 合并等.
这种内核级别的aio缺点也是很明显的, 它是不支持缓存操作的, 即使需要操作的文件块在linux文件缓存中已经存在, 也不会通过读取缓存来代替实际对磁盘的操作, 所以尽管相对于阻塞进程来说有了很大的好转, 但对于单个请求来说, 还是有可能降低实际的处理效率. 因为本可以从缓存中读的数据在使用异步IO后一定会从磁盘读取.
所以异步IO并不是完美的. 如果大部分用户请求对文件的操作都会落到文件缓存中, 那么放弃异步IO可能是更好的选择.
目前, Nginx仅支持读文件的异步IO, 因为正常写入文件往往是写入内存就返回, 相比于异步IO效率明显提高了.


异步IO方法
在理解异步IO方法时, 为了更好的理解这些方法, 与epoll相关方法做比较则更容易理解.
  1. int io_setup(unsigned nr_events, aio_context_t *ctxp);  
  2.         初始化异步IO上下文. 与epoll相比, 类似epoll_create. nr_events表示异步IO上下文可以处理事件的最小个数  
  3.         通过ctxp获得的上下文描述符与epoll_create返回的描述符类似, 贯穿始终.  
  4. int io_submit(aio_context_t ctx_id, long nr, struct iocb **iocbpp);  
  5.         提交文件异步IO操作. iocbpp是提交的事件数组的首个元素地址, nr是提交的事件个数  
  6.         返回值表示成功提交的事件个数. 类似epoll中的 epoll_ctl + EPOLL_CTL_ADD  
  7. int io_cancel(aio_context_t ctx_id, struct iocb *iocb, struct io_event *result);  
  8.         取消某个IO操作  
  9.         类似epoll中的epoll_ctl + EPOLL_CTL_DEL  
  10. int io_getevents(aio_context_t ctx_id, long min_nr, long nr, struct io_event *events, struct timespec *timeout);  
  11.         从已完成的文件异步IO操作队列中读取操作  
  12.         其中, min_nr表示至少获取min_nr个事件; nr表示至多获取nr个事件; events是IO操作完成的事件数组  
  13.         类似epoll中的epoll_wait  
  14. int io_destroy(aio_context_t ctx);  
  15.         销毁异步IO上下文.  
int io_setup(unsigned nr_events, aio_context_t *ctxp);
        初始化异步IO上下文. 与epoll相比, 类似epoll_create. nr_events表示异步IO上下文可以处理事件的最小个数
        通过ctxp获得的上下文描述符与epoll_create返回的描述符类似, 贯穿始终.
int io_submit(aio_context_t ctx_id, long nr, struct iocb **iocbpp);
        提交文件异步IO操作. iocbpp是提交的事件数组的首个元素地址, nr是提交的事件个数
        返回值表示成功提交的事件个数. 类似epoll中的 epoll_ctl + EPOLL_CTL_ADD
int io_cancel(aio_context_t ctx_id, struct iocb *iocb, struct io_event *result);
        取消某个IO操作
        类似epoll中的epoll_ctl + EPOLL_CTL_DEL
int io_getevents(aio_context_t ctx_id, long min_nr, long nr, struct io_event *events, struct timespec *timeout);
        从已完成的文件异步IO操作队列中读取操作
        其中, min_nr表示至少获取min_nr个事件; nr表示至多获取nr个事件; events是IO操作完成的事件数组
        类似epoll中的epoll_wait
int io_destroy(aio_context_t ctx);
        销毁异步IO上下文.

对于一个进程来说, 通常一个异步IO就可以了, 因为一个异步IO可以同时包含很多异步IO操作.
在调用io_setup初始化完成异步IO上下文后, 将会使用io_submit提交异步IO操作.
其中的struct iocb结构体如下:
  1. struct iocb {  
  2.         //存储业务需要的指针. 在Nginx中, 这个字段通常存储对应ngx_event_t对象的指针.  
  3.         //它实际上与io_getevents中返回的io_event结构体的data成员是完全一致的.  
  4.       uint64_t aio_data;  
  5.         //操作码.  比如IO_CMD_PREAD指定异步读操作;  IO_CMD_PWRITE异步写操作;  IO_CMD_FSYNC强制同步等...  
  6.       uint16_t aio_lio_opcode;  
  7.         //请求的优先级  
  8.       int16_t aio_reqprio;   
  9.         //文件描述符  
  10.       uint32_t aio_fields;  
  11.         //读写操作对应的用户态缓冲区.  
  12.       uint64_t aio_buf;  
  13.         //读写操作的字节长度  
  14.       uint64_t aio_nbytes;  
  15.       uint64_t aio_offset;  
  16.         //表示可以设置为IOCB_FLAG_RESFD, 它会告诉内核当有异步IO请求处理完成时使用eventfd进行通知  
  17.         //可与epoll配合使用. 下面会涉及到  
  18.       uint32_t aio_flags;  
  19.         //表示当使用IOCB_FLAG_RESFD标志时, 用于进行事件通知的句柄  
  20.       uint64_t aio_resfd;  
  21.   
  22.         ...  
  23. };  
struct iocb {
        //存储业务需要的指针. 在Nginx中, 这个字段通常存储对应ngx_event_t对象的指针.
        //它实际上与io_getevents中返回的io_event结构体的data成员是完全一致的.
      uint64_t aio_data;
        //操作码.  比如IO_CMD_PREAD指定异步读操作;  IO_CMD_PWRITE异步写操作;  IO_CMD_FSYNC强制同步等...
      uint16_t aio_lio_opcode;
        //请求的优先级
      int16_t aio_reqprio; 
        //文件描述符
      uint32_t aio_fields;
        //读写操作对应的用户态缓冲区.
      uint64_t aio_buf;
        //读写操作的字节长度
      uint64_t aio_nbytes;
      uint64_t aio_offset;
        //表示可以设置为IOCB_FLAG_RESFD, 它会告诉内核当有异步IO请求处理完成时使用eventfd进行通知
        //可与epoll配合使用. 下面会涉及到
      uint32_t aio_flags;
        //表示当使用IOCB_FLAG_RESFD标志时, 用于进行事件通知的句柄
      uint64_t aio_resfd;

        ...
};
在设置好iocb结构体后, 就可以向异步IO提交事件了. 也可以使用io_cancel将已经提交的事件取消.
那么在异步IO操作完成后, 如何使用io_getevents获取已完成的事件呢?
其中io_event结构体如下:
  1. struct io_event {  
  2.         //与io_submit中的iocb结构体的aio_data是一致的  
  3.       uint64_t data;  
  4.         //指向提交事件时候带入的iocb结构体  
  5.       uint64_t obj;  
  6.         //大于或等于0表示成功, 表示读取到的字节数; 否则失败  
  7.       int64_t res;  
  8.         ...  
  9. };  
struct io_event {
        //与io_submit中的iocb结构体的aio_data是一致的
      uint64_t data;
        //指向提交事件时候带入的iocb结构体
      uint64_t obj;
        //大于或等于0表示成功, 表示读取到的字节数; 否则失败
      int64_t res;
        ...
};
根据获取到的io_evnet结构体, 就可以得到已完成的异步IO事件了.
要重点注意的是io_event的data和iocb中的aio_data可以用于传递指针, 所以业务中的数据结构,完成后的回调方法都放在这里.
到这里, 对于异步IO使用的数据结构以及大致使用流程有了初步了解, 下面就配合nginx中的epoll模块来详细分析.


如何使用异步IO
接下来, 将会与Nginx中的epoll模块来分析异步IO的使用实例.
(下面的代码部分均取自 xxx/nginx/src/event/module/ngx_epoll_module.c)
之前, 在分析epoll模块的初始化方法ngx_epoll_init中, 有这么一段代码:
  1. #if (NGX_HAVE_FILE_AIO)  
  2.         ngx_epoll_aio_init(cycle, epcf);  
  3. #endif  
#if (NGX_HAVE_FILE_AIO)
        ngx_epoll_aio_init(cycle, epcf);
#endif
所以, 在epoll启动前, 对于异步IO的初始化工作已经在ngx_epoll_aio_init方法中完成了.
在分析异步IO初始化方法前, 我们可以对其初始化工作做一个猜测, 调用io_setup是必须的, 其他还有什么工作呢?
既然是与epoll模块相互协作的, 那么可以想象, 异步IO与epoll模块之间的联系必须在这里处理好. 这里存在的问题就是epoll与异步IO是如何协作的呢? 异步IO事件完成后如何通知epoll呢? 以及epoll在得到处理完成的异步IO事件后, 如何将其放回业务逻辑中呢?

如果对libevent中的信号集中处理机制有所了解的话, 那么这里第二个问题似乎就明朗了.
libevent中的信号集中处理是什么呢? 我们知道, 信号总是来的很突然, 以及因为信号的特殊, 信号处理函数的内容不宜过多, 所以在libevent中, 每个信号真正要做的事情就不被放在信号处理函数中完成. 那么如何完成信号要真正处理的事件呢? 既然libevent是事件驱动框架, 那么就将每个信号的到来看作一个事件, 将信号与epoll利用管道相联系. 每个需要处理的信号在发生后, 其信号处理函数都只是简单的向管道发送数据(数据往往是每个信号的整型值), 这样, epoll在检查管道中的数据时就会得知某信号发生了, 之后就调用该信号对应的真正的处理函数进行处理. 所以, 管道一端描述符在epoll中注册的事件处理函数的主要工作就是, 读取管道中的内容, 根据不同的信号调用不同的处理函数.

接下来回到Nginx中的异步IO与epoll, 先看代码:
  1. static void  
  2. ngx_epoll_aio_init(ngx_cycle_t *cycle, ngx_epoll_conf_t *epcf)  
  3. {  
  4.     int                 n;  
  5.     struct epoll_event  ee;  
  6.      //这里设置evendfd中的counter初始值为0  
  7.     ngx_eventfd = eventfd(0, 0);  
  8.   
  9.     n = 1;  
  10.      //设置eventfd为非阻塞  
  11.     if (ioctl(ngx_eventfd, FIONBIO, &n) == -1) {  
  12.     }  
  13.      //调用io_setup初始化异步IO事件的上下文  
  14.     if (io_setup(epcf->aio_requests, &ngx_aio_ctx) == -1) {  
  15.     }  
  16.   
  17.      //可以看到, 这里的eventfd对应于libevent中的信号处理机制的管道  
  18.      //在被触发后的任务主要是 1. 得到已完成的异步IO事件   2. 根据不同的事件调用不同的处理函数  
  19.      //所以在分析完初始化函数后, 我们会立即去分析ngx_epoll_eventfd_handler方法来验证我们的猜想.  
  20.      //初始化eventfd的事件, 以及其对应的连接结构体  
  21.     ngx_eventfd_event.data = &ngx_eventfd_conn;  
  22.     ngx_eventfd_event.handler = ngx_epoll_eventfd_handler;  
  23.     ngx_eventfd_event.log = cycle->log;  
  24.     ngx_eventfd_event.active = 1;  
  25.     ngx_eventfd_conn.fd = ngx_eventfd;  
  26.     ngx_eventfd_conn.read = &ngx_eventfd_event;  
  27.     ngx_eventfd_conn.log = cycle->log;  
  28.   
  29.     ee.events = EPOLLIN|EPOLLET;  
  30.     ee.data.ptr = &ngx_eventfd_conn;  
  31.      //将eventfd注册到epoll中, 作为异步IO完成的通知描述符  
  32.     if (epoll_ctl(ep, EPOLL_CTL_ADD, ngx_eventfd, &ee) != -1) {  
  33.         return;  
  34.     }  
  35.      ...   //以下为调用失败后的处理  
  36. }  
static void
ngx_epoll_aio_init(ngx_cycle_t *cycle, ngx_epoll_conf_t *epcf)
{
    int                 n;
    struct epoll_event  ee;
     //这里设置evendfd中的counter初始值为0
    ngx_eventfd = eventfd(0, 0);

    n = 1;
     //设置eventfd为非阻塞
    if (ioctl(ngx_eventfd, FIONBIO, &n) == -1) {
    }
     //调用io_setup初始化异步IO事件的上下文
    if (io_setup(epcf->aio_requests, &ngx_aio_ctx) == -1) {
    }

     //可以看到, 这里的eventfd对应于libevent中的信号处理机制的管道
     //在被触发后的任务主要是 1. 得到已完成的异步IO事件   2. 根据不同的事件调用不同的处理函数
     //所以在分析完初始化函数后, 我们会立即去分析ngx_epoll_eventfd_handler方法来验证我们的猜想.
     //初始化eventfd的事件, 以及其对应的连接结构体
    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;
     //将eventfd注册到epoll中, 作为异步IO完成的通知描述符
    if (epoll_ctl(ep, EPOLL_CTL_ADD, ngx_eventfd, &ee) != -1) {
        return;
    }
     ...   //以下为调用失败后的处理
}
执行完了初始化函数后, 以后在添加异步IO操作同时, 我们会在iocb结构体中声明eventfd作为通知描述符. 这样一来, 当某个异步IO事件完成后, ngx_eventfd句柄就处于可用状态了. 接下来就会调用该句柄对应的ngx_epoll_eventfd_handler方法处理这些完成的异步IO操作.
下面就分析ngx_epoll_eventfd_handler方法的主要内容:
  1. static void  
  2. ngx_epoll_eventfd_handler(ngx_event_t *ev)  
  3. {  
  4.     int               n, events;  
  5.     long              i;  
  6.     uint64_t          ready;  
  7.     ngx_err_t         err;  
  8.     ngx_event_t      *e;  
  9.     ngx_event_aio_t  *aio;  
  10.     struct io_event   event[64];  
  11.     struct timespec   ts;  
  12.      //通过ready得到已经完成的异步IO事件个数.   
  13.      //注意的是ready的大小不受限制, 有多少完成了就会有多少返回出来  
  14.     n = read(ngx_eventfd, &ready, 8);  
  15.   
  16.      ...  
  17.   
  18.     ts.tv_sec = 0;  
  19.     ts.tv_nsec = 0;  
  20.   
  21.      //因为io_getevents函数会指定取出事件的个数, 所以可能不能一次全取出  
  22.      //如果还有没有取出的, 则循环取出  
  23.     while (ready) {  
  24.   
  25.         events = io_getevents(ngx_aio_ctx, 1, 64, event, &ts);  
  26.   
  27.         if (events > 0) {  
  28.                 //如果ready等于0了, 表示全部取完了  
  29.             ready -= events;  
  30.                 //处理每一个已完成的IO异步事件  
  31.             for (i = 0; i < events; i++) {  
  32.                     //在了解结构体的时候, 已经知道data成员其实就是ngx_event_t结构体  
  33.                 e = (ngx_event_t *) (uintptr_t) event[i].data;  
  34.   
  35.                 e->complete = 1;  
  36.                 e->active = 0;  
  37.                 e->ready = 1;  
  38.                      //可以看到, 在异步IO事件中, ngx_event_t结构体的data成员存储的是aio结构体  
  39.                 aio = e->data;  
  40.                 aio->res = event[i].res;  
  41.                     //将事件取出后放到post_events中延后处理  
  42.                 ngx_post_event(e, &ngx_posted_events);  
  43.             }  
  44.   
  45.             continue;  
  46.         }  
  47.         ...  
  48.     }  
  49. }  
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];
    struct timespec   ts;
     //通过ready得到已经完成的异步IO事件个数. 
     //注意的是ready的大小不受限制, 有多少完成了就会有多少返回出来
    n = read(ngx_eventfd, &ready, 8);

     ...

    ts.tv_sec = 0;
    ts.tv_nsec = 0;

     //因为io_getevents函数会指定取出事件的个数, 所以可能不能一次全取出
     //如果还有没有取出的, 则循环取出
    while (ready) {

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

        if (events > 0) {
                //如果ready等于0了, 表示全部取完了
            ready -= events;
                //处理每一个已完成的IO异步事件
            for (i = 0; i < events; i++) {
                    //在了解结构体的时候, 已经知道data成员其实就是ngx_event_t结构体
                e = (ngx_event_t *) (uintptr_t) event[i].data;

                e->complete = 1;
                e->active = 0;
                e->ready = 1;
                     //可以看到, 在异步IO事件中, ngx_event_t结构体的data成员存储的是aio结构体
                aio = e->data;
                aio->res = event[i].res;
                    //将事件取出后放到post_events中延后处理
                ngx_post_event(e, &ngx_posted_events);
            }

            continue;
        }
        ...
    }
}
可以看到, 之前我们的猜测是正确的, eventfd的处理函数主要就是对不同的事件调用其不同的处理函数.
只是这里将处理函数调用的时机延后了.
所以, 整个网络事件的驱动机制就是这样通过ngx_evnetfd通知描述符和ngx_epoll_eventfd_handler回调方法, 并与文件异步IO事件结合起来的
当我们在分析eventfd的处理函数的同时, 我们可能对其中ngx_event_t对象的data成员存储ngx_event_aio_t结构体对象感到不解, 所以下面对异步IO事件提交进行分析:
(对于其中的file结构体尚不做解释, 暂时可以不理解)
  1. ssize_t  
  2. ngx_file_aio_read(ngx_file_t *file, u_char *buf, size_t size, off_t offset,  
  3.     ngx_pool_t *pool)  
  4. {  
  5.     ngx_err_t         err;  
  6.     struct iocb      *piocb[1];  
  7.     ngx_event_t      *ev;  
  8.     ngx_event_aio_t  *aio;  
  9.   
  10.     aio = file->aio;  
  11.      //得到当前要操作的事件对象  
  12.     ev = &aio->event;  
  13.   
  14.      ......  
  15.      //想要提交异步IO事件, 首先就需要初始化iocb结构体  
  16.     ngx_memzero(&aio->aiocb, sizeof(struct iocb));  
  17.      //之前说过, iocb结构体中的aio_data对象与使用io_getevents得到的io_event结构体中的data是一致的.  
  18.      //所以这个指针指向的事件对象会被传递  
  19.      //所以在获取完成的异步事件后通过io_event结构体的data成员得到的指针也是指向这个ev的  
  20.     aio->aiocb.aio_data = (uint64_t) (uintptr_t) ev;  
  21.     aio->aiocb.aio_lio_opcode = IOCB_CMD_PREAD;  
  22.     aio->aiocb.aio_fildes = file->fd;  
  23.     aio->aiocb.aio_buf = (uint64_t) (uintptr_t) buf;  
  24.     aio->aiocb.aio_nbytes = size;  
  25.     aio->aiocb.aio_offset = offset;  
  26.      //解释结构体期间就对这个标志说明过, 这是异步事件与eventd和epoll相关联的实现方式  
  27.     aio->aiocb.aio_flags = IOCB_FLAG_RESFD;  
  28.      //对应的eventfd就是在epoll中注册过的eventfd  
  29.     aio->aiocb.aio_resfd = ngx_eventfd;  
  30.      //异步事件完成后, 此事件会被调用下面这个回调方法  
  31.     ev->handler = ngx_file_aio_event_handler;  
  32.   
  33.     piocb[0] = &aio->aiocb;  
  34.   
  35.     if (io_submit(ngx_aio_ctx, 1, piocb) == 1) {  
  36.         ev->active = 1;  
  37.         ev->ready = 0;  
  38.         ev->complete = 0;  
  39.   
  40.         return NGX_AGAIN;  
  41.     }  
  42.   
  43.     ...  
  44. }  
ssize_t
ngx_file_aio_read(ngx_file_t *file, u_char *buf, 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;

     ......
     //想要提交异步IO事件, 首先就需要初始化iocb结构体
    ngx_memzero(&aio->aiocb, sizeof(struct iocb));
     //之前说过, iocb结构体中的aio_data对象与使用io_getevents得到的io_event结构体中的data是一致的.
     //所以这个指针指向的事件对象会被传递
     //所以在获取完成的异步事件后通过io_event结构体的data成员得到的指针也是指向这个ev的
    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;
     //解释结构体期间就对这个标志说明过, 这是异步事件与eventd和epoll相关联的实现方式
    aio->aiocb.aio_flags = IOCB_FLAG_RESFD;
     //对应的eventfd就是在epoll中注册过的eventfd
    aio->aiocb.aio_resfd = ngx_eventfd;
     //异步事件完成后, 此事件会被调用下面这个回调方法
    ev->handler = ngx_file_aio_event_handler;

    piocb[0] = &aio->aiocb;

    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结构体, 但从上面函数来看, 它提供了对iocb结构体的包装以及其他一些必要的信息.
看上面这个函数, 唯一的疑惑点在于ngx_file_aio_event_handler方法, 它干了什么呢? 按照我们的理解, 此函数应该是异步事件完成后, 真正处理逻辑的方法
但它的名字看上去却不像, 仔细看其源码:
  1. static void  
  2. ngx_file_aio_event_handler(ngx_event_t *ev)  
  3. {  
  4.     ngx_event_aio_t  *aio;  
  5.   
  6.     aio = ev->data;  
  7.   
  8.     aio->handler(ev);  
  9. }  
static void
ngx_file_aio_event_handler(ngx_event_t *ev)
{
    ngx_event_aio_t  *aio;

    aio = ev->data;

    aio->handler(ev);
}
原来, 真正在异步IO事件结束后进行逻辑处理的函数被放在了ngx_event_aio_t结构体中.
那么索性看一下ngx_event_aio_t结构体内容:
  1. struct ngx_event_aio_s {  
  2.     void                      *data;  
  3.      //异步IO结束后真正用来处理逻辑的回调函数  
  4.     ngx_event_handler_pt       handler;  
  5.    
  6.      ...  
  7.   
  8.     ngx_aiocb_t                aiocb;  
  9.     ngx_event_t                event;  
  10. };  
struct ngx_event_aio_s {
    void                      *data;
     //异步IO结束后真正用来处理逻辑的回调函数
    ngx_event_handler_pt       handler;
 
     ...

    ngx_aiocb_t                aiocb;
    ngx_event_t                event;
};
可以想象的是, 如果某个消费者模块需要处理异步IO事件, 那么其应该会在ngx_event_aio_s结构体中设置一个回调函数来处理自己的业务逻辑
好了, 到这里, 无论是添加异步IO事件, 将异步IO事件与epoll相结合还是异步IO事件完成后的回调方法, 这里都有详细的介绍了.



自己实现:
在自己进行异步IO编程的时候, 发现Nginx中的struct iocb成员可能已经被Nginx修改过别名了, 所以下面才是 真正的struct iocb结构体
  1. struct iocb {  
  2.         void *data; /* Return in the io completion event */  
  3.         unsigned key; /*r use in identifying io requests */  
  4.         short aio_lio_opcode;  
  5.         short aio_reqprio;  
  6.         int aio_fildes;  
  7.   
  8.         union {  
  9.                 struct io_iocb_common c;  
  10.                 struct io_iocb_vector v;  
  11.                 struct io_iocb_poll poll;  
  12.                 struct io_iocb_sockaddr saddr;  
  13.         } u;  
  14. };  
  15.   
  16. struct io_iocb_common {  
  17.         void *buf;  
  18.         unsigned long nbytes;  
  19.         long long offset;  
  20.         unsigned flags;  
  21.         unsigned resfd;  
  22. };  
struct iocb {
        void *data; /* Return in the io completion event */
        unsigned key; /*r use in identifying io requests */
        short aio_lio_opcode;
        short aio_reqprio;
        int aio_fildes;

        union {
                struct io_iocb_common c;
                struct io_iocb_vector v;
                struct io_iocb_poll poll;
                struct io_iocb_sockaddr saddr;
        } u;
};

struct io_iocb_common {
        void *buf;
        unsigned long nbytes;
        long long offset;
        unsigned flags;
        unsigned resfd;
};

在对iocb结构体进行初始化时, 系统提供了一些函数
  1. //设置读写类型的iocb  
  2. void io_prep_pwrite(struct iocb *iocb, int fd, void *buf, size_t count, long long offset);  
  3. void io_prep_pread(struct iocb *iocb, int fd, void *buf, size_t count, long long offset);  
  4. //下面两个暂时不知道..  
  5. void io_prep_pwritev(struct iocb *iocb, int fd, const struct iovec *iov, int iovcnt, long long offset);  
  6. void io_prep_preadv(struct iocb *iocb, int fd, const struct iovec *iov, int iovcnt, long long offset);  
  7. //设置异步IO事件完成后的函数句柄, 注意, 这里函数是被挂在iocb结构体的data成员中的, 所以如果有数据要放在data上, 那就把回调函数放到自己的结构体挂在data上  
  8. void io_set_callback(struct iocb *iocb, io_callback_t cb);  
  9. 对于设置的函数, linux给出的原型如下:  
  10.       void callback_function(io_context_t ctx, struct iocb *iocb, long res, long res2);  
  11.         参数: 上下文.  iocb结构体.  读取到的字节数.  不清楚..  
//设置读写类型的iocb
void io_prep_pwrite(struct iocb *iocb, int fd, void *buf, size_t count, long long offset);
void io_prep_pread(struct iocb *iocb, int fd, void *buf, size_t count, long long offset);
//下面两个暂时不知道..
void io_prep_pwritev(struct iocb *iocb, int fd, const struct iovec *iov, int iovcnt, long long offset);
void io_prep_preadv(struct iocb *iocb, int fd, const struct iovec *iov, int iovcnt, long long offset);
//设置异步IO事件完成后的函数句柄, 注意, 这里函数是被挂在iocb结构体的data成员中的, 所以如果有数据要放在data上, 那就把回调函数放到自己的结构体挂在data上
void io_set_callback(struct iocb *iocb, io_callback_t cb);
对于设置的函数, linux给出的原型如下:
      void callback_function(io_context_t ctx, struct iocb *iocb, long res, long res2);
        参数: 上下文.  iocb结构体.  读取到的字节数.  不清楚..

将异步IO与epoll结合的具体流程如下:

1. 创建一个eventfd
  1. efd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);  
        efd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);
2. 将eventfd设置到iocb中
  1. io_set_eventfd(iocb, efd);  
        io_set_eventfd(iocb, efd);
3. 交接AIO请求
  1. io_submit(ctx, NUM_EVENTS, iocb);  
        io_submit(ctx, NUM_EVENTS, iocb);
4. 创建一个epollfd,并将eventfd加到epoll中
  1. epfd = epoll_create(1);  
  2. epoll_ctl(epfd, EPOLL_CTL_ADD, efd, &epevent);  
  3. epoll_wait(epfd, &epevent, 1, -1);  
        epfd = epoll_create(1);
        epoll_ctl(epfd, EPOLL_CTL_ADD, efd, &epevent);
        epoll_wait(epfd, &epevent, 1, -1);
5. 当eventfd可读时,从eventfd读出完成IO请求的数量,并调用io_getevents获取这些IO
  1. read(efd, &finished_aio, sizeof(finished_aio);  
  2. r = io_getevents(ctx, 1, NUM_EVENTS, events, &tms);  
        read(efd, &finished_aio, sizeof(finished_aio);
        r = io_getevents(ctx, 1, NUM_EVENTS, events, &tms);

实例:
以下是我个人编写的例子, 这里过程很直白, 而不是将每个部分用函数分割开来, 是为了理解起来更顺畅 :
(此文件名为 as.c)
  1. #define _GNU_SOURCE  
  2. #define __STDC_FORMAT_MACROS  
  3.   
  4. #include <stdio.h>  
  5. #include <errno.h>  
  6. #include <libaio.h>  
  7. #include <sys/eventfd.h>  
  8. #include <sys/epoll.h>  
  9. #include <stdlib.h>  
  10. #include <sys/types.h>  
  11. #include <unistd.h>  
  12. #include <stdint.h>  
  13. #include <sys/stat.h>  
  14. #include <fcntl.h>  
  15. #include <inttypes.h>  
  16.   
  17. void cf(io_context_t ctx, struct iocb *iocb, long res, long res2);  
  18.   
  19. int main(int ac, char *av[])  
  20. {  
  21.     int epfd;                //epoll fd  
  22.     int evfd;                //eventfd fd  
  23.     int fd;                    //file fd  
  24.     io_context_t ctx;             //异步IO fd  
  25.     char filename[20];            //要异步IO处理的文件  
  26.     void *buf;                //异步IO读取出来的内容存放地  
  27.     struct epoll_event ev_event;        //evfd 存入 epoll 的 epoll_event结构  
  28.     struct epoll_event *events_list;    //epoll_wait用  
  29.     struct iocb cb;                //一个异步IO读取文件事件  
  30.     struct iocb *pcbs[1];            //io_submit参数, 包含上面的cb  
  31.     struct timespec ts;            //io_getevents定时器  
  32.     int n;  
  33.     uint64_t ready;                //已完成的异步IO事件  
  34.     struct io_event *events_ret;        //io_getevents参数  
  35.   
  36.     if(ac != 2){  
  37.         fprintf(stderr, "%s [pathname]\n", av[0]);  
  38.         exit(2);  
  39.     }  
  40.   
  41.     //初始化epoll  
  42.     epfd = epoll_create(10);  
  43.     if(epfd < 0){  
  44.         perror("epoll_create");  
  45.         exit(4);  
  46.     }  
  47.   
  48.     //初始化eventfd, 并将其描述符加入epoll  
  49.     //eventfd对应事件直接在此main中写出  
  50.     evfd = eventfd(0, 0);  
  51.     if(evfd < 0){  
  52.         perror("eventfd");  
  53.         exit(5);  
  54.     }  
  55.     ev_event.events = EPOLLIN | EPOLLET;  
  56.     ev_event.data.ptr = NULL;  
  57.     if(epoll_ctl(epfd, EPOLL_CTL_ADD, evfd, &ev_event) != 0){  
  58.         perror("epoll_ctl");  
  59.         exit(6);  
  60.     }  
  61.   
  62.     memset(filename, '\0', 20);  
  63.     strncpy(filename, av[1], 19);  
  64.     //初始化异步IO上下文  
  65.     ctx = 0;  
  66.     if(io_setup(1024, &ctx) != 0){  
  67.         perror("io_setup");  
  68.         exit(3);  
  69.     }  
  70.   
  71.     //添加异步IO事件  
  72.     fd = open(filename, O_RDONLY | O_CREAT, 0644);  
  73.     if(fd < 0){  
  74.         perror("open");  
  75.         exit(7);  
  76.     }  
  77.     posix_memalign(&buf, 512, 1024);  
  78.     memset(&cb, '\0'sizeof(struct iocb));  
  79.     io_prep_pread(&cb, fd, buf, 1024, 0);  
  80.     io_set_eventfd(&cb, evfd);  
  81.     io_set_callback(&cb, cf);  
  82.     pcbs[0] = &cb;  
  83.     if(io_submit(ctx, 1, pcbs) != 1){  
  84.         perror("io_submit");  
  85.         exit(8);  
  86.     }  
  87.   
  88.     //调用epoll_wait等待异步IO事件完成  
  89.     events_list = (struct epoll_event *)malloc(sizeof(struct epoll_event) * 32);  
  90.     while(1){  
  91.         n = epoll_wait(epfd, events_list, 32, -1);  
  92.         if(n <= 0){  
  93.             if(errno != EINTR){  
  94.                 perror("epoll_wait");  
  95.                 exit(9);  
  96.             }  
  97.         }  
  98.         else  
  99.             break;  
  100.     }  
  101.   
  102.     //读取已完成的异步IO事件数量  
  103.     n = read(evfd, &ready, sizeof(ready));  
  104.     if(n != 8){  
  105.         perror("read error");  
  106.         exit(10);  
  107.     }  
  108.   
  109.     //取出完成的异步IO事件并处理  
  110.     ts.tv_sec = 0;  
  111.     ts.tv_nsec = 0;  
  112.     events_ret = (struct io_event *)malloc(sizeof(struct io_event) * 32);  
  113.     n = io_getevents(ctx, 1, 32, events_ret, &ts);  
  114.   
  115.     printf("log: %d events are ready;  get %d events\n",ready, n);  
  116.   
  117.     ((io_callback_t)(events_ret[0].data))(ctx, events_ret[0].obj, events_ret[0].res, events_ret[0].res2);  
  118.   
  119.     //收尾  
  120.     io_destroy(ctx);  
  121.     free(buf);  
  122.     close(epfd);  
  123.     close(fd);  
  124.     close(evfd);  
  125.     return 0;  
  126. }  
  127.   
  128. void cf(io_context_t ctx, struct iocb *iocb, long res, long res2){  
  129.     printf("can read %d bytes, and in fact has read %d bytes\n", iocb->u.c.nbytes, res);  
  130.     printf("the content is :\n%s", iocb->u.c.buf);  
  131. }  
#define _GNU_SOURCE
#define __STDC_FORMAT_MACROS

#include <stdio.h>
#include <errno.h>
#include <libaio.h>
#include <sys/eventfd.h>
#include <sys/epoll.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdint.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <inttypes.h>

void cf(io_context_t ctx, struct iocb *iocb, long res, long res2);

int main(int ac, char *av[])
{
    int epfd;                //epoll fd
    int evfd;                //eventfd fd
    int fd;                    //file fd
    io_context_t ctx;             //异步IO fd
    char filename[20];            //要异步IO处理的文件
    void *buf;                //异步IO读取出来的内容存放地
    struct epoll_event ev_event;        //evfd 存入 epoll 的 epoll_event结构
    struct epoll_event *events_list;    //epoll_wait用
    struct iocb cb;                //一个异步IO读取文件事件
    struct iocb *pcbs[1];            //io_submit参数, 包含上面的cb
    struct timespec ts;            //io_getevents定时器
    int n;
    uint64_t ready;                //已完成的异步IO事件
    struct io_event *events_ret;        //io_getevents参数

    if(ac != 2){
        fprintf(stderr, "%s [pathname]\n", av[0]);
        exit(2);
    }

    //初始化epoll
    epfd = epoll_create(10);
    if(epfd < 0){
        perror("epoll_create");
        exit(4);
    }

    //初始化eventfd, 并将其描述符加入epoll
    //eventfd对应事件直接在此main中写出
    evfd = eventfd(0, 0);
    if(evfd < 0){
        perror("eventfd");
        exit(5);
    }
    ev_event.events = EPOLLIN | EPOLLET;
    ev_event.data.ptr = NULL;
    if(epoll_ctl(epfd, EPOLL_CTL_ADD, evfd, &ev_event) != 0){
        perror("epoll_ctl");
        exit(6);
    }

    memset(filename, '\0', 20);
    strncpy(filename, av[1], 19);
    //初始化异步IO上下文
    ctx = 0;
    if(io_setup(1024, &ctx) != 0){
        perror("io_setup");
        exit(3);
    }

    //添加异步IO事件
    fd = open(filename, O_RDONLY | O_CREAT, 0644);
    if(fd < 0){
        perror("open");
        exit(7);
    }
    posix_memalign(&buf, 512, 1024);
    memset(&cb, '\0', sizeof(struct iocb));
    io_prep_pread(&cb, fd, buf, 1024, 0);
    io_set_eventfd(&cb, evfd);
    io_set_callback(&cb, cf);
    pcbs[0] = &cb;
    if(io_submit(ctx, 1, pcbs) != 1){
        perror("io_submit");
        exit(8);
    }

    //调用epoll_wait等待异步IO事件完成
    events_list = (struct epoll_event *)malloc(sizeof(struct epoll_event) * 32);
    while(1){
        n = epoll_wait(epfd, events_list, 32, -1);
        if(n <= 0){
            if(errno != EINTR){
                perror("epoll_wait");
                exit(9);
            }
        }
        else
            break;
    }

    //读取已完成的异步IO事件数量
    n = read(evfd, &ready, sizeof(ready));
    if(n != 8){
        perror("read error");
        exit(10);
    }

    //取出完成的异步IO事件并处理
    ts.tv_sec = 0;
    ts.tv_nsec = 0;
    events_ret = (struct io_event *)malloc(sizeof(struct io_event) * 32);
    n = io_getevents(ctx, 1, 32, events_ret, &ts);

    printf("log: %d events are ready;  get %d events\n",ready, n);

    ((io_callback_t)(events_ret[0].data))(ctx, events_ret[0].obj, events_ret[0].res, events_ret[0].res2);

    //收尾
    io_destroy(ctx);
    free(buf);
    close(epfd);
    close(fd);
    close(evfd);
    return 0;
}

void cf(io_context_t ctx, struct iocb *iocb, long res, long res2){
    printf("can read %d bytes, and in fact has read %d bytes\n", iocb->u.c.nbytes, res);
    printf("the content is :\n%s", iocb->u.c.buf);
}


结果:
  1. $ gcc -o as as.c  
  2. $ ./a3 test.txt   
  3. log: 1 events are ready;  get 1 events  
  4. can read 1024 bytes, and in fact has read 61 bytes  
  5. the content is :  
  6. qqqqq  
  7. qqqqq  
  8. qqqqq  
  9. qqqqq  
  10. fewfqqqqq  
  11. fwegw  
  12. gwegmewrgjewgnwgnnwj  
$ gcc -o as as.c
$ ./a3 test.txt 
log: 1 events are ready;  get 1 events
can read 1024 bytes, and in fact has read 61 bytes
the content is :
qqqqq
qqqqq
qqqqq
qqqqq
fewfqqqqq
fwegw
gwegmewrgjewgnwgnnwj


其他具体的代码, 参考他人博客...

        http://blog.sina.com.cn/s/blog_6b19f21d0100znza.html
        http://blog.csdn.net/heyutao007/article/details/7065166

主要参考:
      http://www.kuqin.com/linux/20120908/330333.html
        深入理解Nginx
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值