技术头条 - 一个快速在微博传播文章的方式     搜索本站
您现在的位置首页 --> 源码分析 --> linux异步IO编程实例分析

linux异步IO编程实例分析

浏览:1807次  出处信息

在Direct IO模式下,异步是非常有必要的(因为绕过了pagecache,直接和磁盘交互)。linux Native AIO正是基于这种场景设计的,具体的介绍见:KernelAsynchronousI/O (AIO) SupportforLinux。下面我们就来分析一下AIO编程的相关知识。

           阻塞模式下的IO过程如下:

    

     int fd = open(const char *pathname, int flags, mode_t mode);

    ssize_t pread(int fd, void *buf, size_t count, off_t offset);

    ssize_t pwrite(int fd, const void *buf, size_t count, off_t offset);

    int close(int fd);

    因为整个过程会等待read/write的返回,所以不需要任何额外的数据结构。但异步IO的思想是:应用程序不能阻塞在昂贵的系统调用上让CPU睡大觉,而是将IO操作抽象成一个个的任务单元提交给内核,内核完成IO任务后将结果放在应用程序可以取到的地方。这样在底层做I/O的这段时间内,CPU可以去干其他的计算任务。但异步的IO任务批量的提交和完成,必须有自身可描述的结构,最重要的两个就是iocb和io_event。

libaio中的structs

    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是提交IO任务时用到的,可以完整地描述一个IO请求:

    data是留给用来自定义的指针:可以设置为IO完成后的callback函数;

    aio_lio_opcode表示操作的类型:IO_CMD_PWRITE | IO_CMD_PREAD;

    aio_fildes是要操作的文件:fd;

    io_iocb_common中的buf, nbytes, offset分别记录的IO请求的mem buffer,大小和偏移。

    struct io_event {

            void *data;

            struct iocb *obj;

            unsigned long res;

            unsigned long res2;

    };

    io_event是用来描述返回结果的:

    obj就是之前提交IO任务时的iocb;

    res和res2来表示IO任务完成的状态。

libaio提供的API和完成IO的过程

    libaio提供的API有:io_setup, io_submit, io_getevents, io_destroy。

    1. 建立IO任务

    int io_setup (int maxevents, io_context_t *ctxp);

    io_context_t对应内核中一个结构,为异步IO请求提供上下文环境。注意在setup前必须将io_context_t初始化为0。

    当然,这里也需要open需要操作的文件,注意设置O_DIRECT标志。

    2.提交IO任务

    long io_submit (aio_context_t ctx_id, long nr, struct iocb **iocbpp);

    提交任务之前必须先填充iocb结构体,libaio提供的包装函数说明了需要完成的工作:

    void io_prep_pread(struct iocb *iocb, int fd, void *buf, size_t count, long long offset)

    {

            memset(iocb, 0, sizeof(*iocb));

            iocb->aio_fildes = fd;

            iocb->aio_lio_opcode = IO_CMD_PREAD;

            iocb->aio_reqprio = 0;

            iocb->u.c.buf = buf;

            iocb->u.c.nbytes = count;

            iocb->u.c.offset = offset;

    }

    void io_prep_pwrite(struct iocb *iocb, int fd, void *buf, size_t count, long long offset)

    {

            memset(iocb, 0, sizeof(*iocb));

            iocb->aio_fildes = fd;

            iocb->aio_lio_opcode = IO_CMD_PWRITE;

            iocb->aio_reqprio = 0;

            iocb->u.c.buf = buf;

            iocb->u.c.nbytes = count;

            iocb->u.c.offset = offset;

    }

    这里注意读写的buf都必须是按扇区对齐的,可以用posix_memalign来分配。

    3.获取完成的IO

    long io_getevents (aio_context_t ctx_id, long min_nr, long nr, struct io_event *events, struct timespec *timeout);

    这里最重要的就是提供一个io_event数组给内核来copy完成的IO请求到这里,数组的大小是io_setup时指定的maxevents。

    timeout是指等待IO完成的超时时间,设置为NULL表示一直等待所有到IO的完成。

    4.销毁IO任务

    int io_destroy (io_context_t ctx);

libaio和epoll的结合

    在异步编程中,任何一个环节的阻塞都会导致整个程序的阻塞,所以一定要避免在io_getevents调用时阻塞式的等待。还记得io_iocb_common中的flags和resfd吗?看看libaio是如何提供io_getevents和事件循环的结合:

    void io_set_eventfd(struct iocb *iocb, int eventfd)

    {

            iocb->u.c.flags |= (1 << 0) /* IOCB_FLAG_RESFD */;

            iocb->u.c.resfd = eventfd;

    }

    这里的resfd是通过系统调用eventfd生成的。

    int eventfd(unsigned int initval, int flags);

    eventfd是linux 2.6.22内核之后加进来的syscall,作用是内核用来通知应用程序发生的事件的数量,从而使应用程序不用频繁地去轮询内核是否有时间发生,而是有内核将发生事件的数量写入到该fd,应用程序发现fd可读后,从fd读取该数值,并马上去内核读取。

    有了eventfd,就可以很好地将libaio和epoll事件循环结合起来:

    1. 创建一个eventfd

    efd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);

    2. 将eventfd设置到iocb中

    io_set_eventfd(iocb, efd);

    3. 交接AIO请求

    io_submit(ctx, NUM_EVENTS, iocb);

    4. 创建一个epollfd,并将eventfd加到epoll中

    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

    read(efd, &finished_aio, sizeof(finished_aio);

    r = io_getevents(ctx, 1, NUM_EVENTS, events, &tms);

一个完整的编程实例

    http://blog.sina.com.cn/s/blog_6b19f21d0100znza.html

    以上就是linux 异步IO编程的一些基础知识,希望对感兴趣的同学或多或少有些帮忙,谢谢。

QQ技术交流群:445447336,欢迎加入!
扫一扫订阅我的微信号:IT技术博客大学习
© 2009 - 2024 by blogread.cn 微博:@IT技术博客大学习

京ICP备15002552号-1