在Direct IO模式下,異步是非常有必要的(因為繞過了pagecache,直接和磁盤交互)。linux Native AIO正是基于這種場景設計的,具體的介紹見:KernelAsynchronousI/O (AIOSupportforLinux。下面我們就來分析一下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的思想是:應用程序不能阻塞在昂貴的系統(tǒng)調用上讓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任務完成的狀態(tài)。

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請求提供上下文環(huán)境。注意在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都必須是按扇區(qū)對齊的,可以用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的結合

在異步編程中,任何一個環(huán)節(jié)的阻塞都會導致整個程序的阻塞,所以一定要避免在io_getevents調用時阻塞式的等待。還記得io_iocb_common中的flags和resfd嗎?看看libaio是如何提供io_getevents和事件循環(huán)的結合:

void io_set_eventfd(struct iocb *iocb, int eventfd)

{

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

        iocb->u.c.resfd = eventfd;

}

這里的resfd是通過系統(tǒng)調用eventfd生成的。

int eventfd(unsigned int initval, int flags);

eventfd是linux 2.6.22內核之后加進來的syscall,作用是內核用來通知應用程序發(fā)生的事件的數量,從而使應用程序不用頻繁地去輪詢內核是否有時間發(fā)生,而是有內核將發(fā)生事件的數量寫入到該fd,應用程序發(fā)現(xiàn)fd可讀后,從fd讀取該數值,并馬上去內核讀取。

有了eventfd,就可以很好地將libaio和epoll事件循環(huán)結合起來:

1. 創(chuàng)建一個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. 創(chuàng)建一個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編程的一些基礎知識,希望對感興趣的同學或多或少有些幫忙,謝謝。