注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

网易杭研后台技术中心的博客

 
 
 
 
 

日志

 
 

Linux下原生异步IO接口libaio介绍  

来自haichengsun123   2013-06-25 19:18:03|  分类: 默认分类 |举报 |字号 订阅

  下载LOFTER 我的照片书  |
    在调研 fio的实现时,接触了libaio的使用方式。由于fio 的io engine发送及接受数据的流程是按照liaio库的方式进行的。所以初步使用了libaio。现总结如下。
几点说明
    本文的重点在于libaio的使用方式。所以
 1.对什么是同步、异步及阻塞、非阻塞IO,请参考相应资料。比较权威的资料是Richard Stevens的“UNIXNetwork Programming Volume 1, Third Edition: The Sockets Networking ”,6.2节“I/O Models ”。其他网络资料比如 同步,异步,阻塞,非阻塞 阻塞,非阻塞IO和同步,异步IO
2.对POSIX AIO及libaio的区别,请参看POSIX AIO and libaio on Linuxlibaio是原生的 linux aio,行为更为低级;POSXI AIO是在用户空间模拟异步IO的功能,不需要内核的支持。
3.linux网络编程中的异步IO接口epoll、kqueue等在此不做介绍,这方面资料也相对较多。
4.学习libaio是为了调研fio的实现,并没有深入使用,目前网上介绍这方面的资料相对较少,权当抛砖引玉。
liaio介绍
  linux kernel 提供了5个系统调用来实现异步IO。文中最后介绍的是包装了这些系统调用的用户空间的函数。
libaio系统调用
AIO系统调用总共五个,后面会一一介绍。

* int io_setup(unsigned nr_events,  aio_context_t *ctxp);
* int io_destroy(aio_context_t ctx);
* int io_submit(aio_context_t ctx,  long nr,  struct iocb *cbp[]);
* int io_cancel(aio_context_t ctx,  struct iocb *,  struct io_event *result);
* int io_getevents(aio_context_t ctx, long min_nr, long nr, struct io_event *events, struct timespec *timeout);


1.异步IO上下文 aio_context_t
>> aio_context_t.c >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

#define _GNU_SOURCE     /* syscall() is not POSIX */

#include <stdio.h>      /* for perror() */
#include <unistd.h>     /* for syscall() */
#include <sys/syscall.h>    /* for __NR_* definitions */
#include <linux/aio_abi.h>  /* for AIO types and constants */

inline int io_setup(unsigned nr, aio_context_t *ctxp)
{
    return syscall(__NR_io_setup, nr, ctxp);
}

inline int io_destroy(aio_context_t ctx)
{
    return syscall(__NR_io_destroy, ctx);
}
int main()
{
    aio_context_t ctx;
    int ret;
    ctx = 0;
    ret = io_setup(128, &ctx);
    if (ret < 0) {
        perror("io_setup error");
        return -1;
    }
    printf("after io_setup ctx:%Ld\n",ctx);
    ret = io_destroy(ctx);
    if (ret < 0) {
        perror("io_destroy error");
        return -1;
    }
    printf("after io_destroy ctx:%Ld\n",ctx);
    return 0;
}

系统调用io_setup会创建一个所谓的"AIO上下文"(即aio_context,后文也叫‘AIO context’等)结构体到在内核中。aio_context是用以内核实现异步AIO的数据结构。它其实是一个无符号整形,位于头文件 /usr/include/linux/aio_abi.h

typedef unsigned long   aio_context_t;

每个进程都可以有多个aio_context_t。传入io_setup的第一个参数在这里是128,表示同时驻留在上下文中的IO请求的个数;第二个参数是一个指针,内核会填充这个值。
io_destroy的作用是销毁这个上下文aio_context_t
上面的例子很简单,创建一个aio_context_t并销毁。
编译运行,编译时需要连接库libaio(-laio):

$ gcc -Wall aio_context_t.c  -o aio_context_t -laio
$ ./aio_context_t
after io_setup ctx:139730712117248
after io_destroy ctx:139730712117248


2.提交并查询IO
#define _GNU_SOURCE /* syscall() is not POSIX */
#include <stdio.h> /* for perror() */
#include <unistd.h> /* for syscall() */
#include <sys/syscall.h> /* for __NR_* definitions */
#include <linux/aio_abi.h> /* for AIO types and constants */
#include <fcntl.h> /* O_RDWR */
#include <string.h> /* memset() */
#include <inttypes.h> /* uint64_t */

inline int io_setup(unsigned nr, aio_context_t *ctxp)
{
return syscall(__NR_io_setup, nr, ctxp);
}

inline int io_destroy(aio_context_t ctx)
{
return syscall(__NR_io_destroy, ctx);
}

inline int io_submit(aio_context_t ctx, long nr, struct iocb **iocbpp)
{
return syscall(__NR_io_submit, ctx, nr, iocbpp);
}

inline int io_getevents(aio_context_t ctx, long min_nr, long max_nr,
struct io_event *events, struct timespec *timeout)
{
return syscall(__NR_io_getevents, ctx, min_nr, max_nr, events, timeout);
}

int main()
{
aio_context_t ctx;
struct iocb cb;
struct iocb *cbs[1];
char data[4096];
struct io_event events[1];
int ret;
int fd;

int i ;
for(i=0;i<4096;i++)
{
data[i]=i%50+60;
}
fd = open("./testfile", O_RDWR | O_CREAT,S_IRWXU);
if (fd < 0) {
perror("open error");
return -1;
}
ctx = 0;

ret = io_setup(128, &ctx);
printf("after io_setup ctx:%ld",ctx);
if (ret < 0) {
perror("io_setup error");
return -1;
}
/* setup I/O control block */
memset(&cb, 0, sizeof(cb));
cb.aio_fildes = fd;
cb.aio_lio_opcode = IOCB_CMD_PWRITE;

/* command-specific options */
cb.aio_buf = (uint64_t)data;
cb.aio_offset = 0;
cb.aio_nbytes = 4096;

cbs[0] = &cb;

ret = io_submit(ctx, 1, cbs);
if (ret != 1) {
if (ret < 0)
perror("io_submit error");
else
fprintf(stderr, "could not sumbit IOs");
return -1;
}
/* get the reply */
ret = io_getevents(ctx, 1, 1, events, NULL);
printf("%d\n", ret);
struct iocb * result = (struct iocb *)events[0].obj;
printf("reusult:%Ld",result->aio_buf);
ret = io_destroy(ctx);
if (ret < 0) {
perror("io_destroy error");
return -1;
}
return 0;
}
1)  每一个提交的IO请求用结构体struct iocb来表示。
   首先初始化这个结构体为全零: memset(&cb, 0, sizeof(cb));
   然后初始化文件描述符(cb.aio_fildes = fd)和AIO 命令(cb.aio_lio_opcode = IOCB_CMD_PWRITE)
     文件描述符对应上文所打开的文件。本例中是./testfile.
   内核当前支持的AIO 命令有

   IOCB_CMD_PREAD
   读; 对应系统调用pread().

   IOCB_CMD_PWRITE
   写,对应系统调用pwrite().

   IOCB_CMD_FSYNC
   同步文件数据到磁盘,对应系统调用fsync()

   IOCB_CMD_FDSYNC
   同步文件数据到磁盘,对应系统调用fdatasync()
     IOCB_CMD_PREADV
   读,对应系统调用readv()

   IOCB_CMD_PWRITEV
   写,对应系统调用writev()
   IOCB_CMD_NOOP
   只是内核使用
    cb.aio_buf = (uint64_t)data;其中的data对应要读或要写入的数据的内存地址。
      cb.aio_offset=0 表示文件的绝对偏移量
 2) 调用io_submit
函数原型int io_submit(aio_context_t ctx,  long nr,  struct iocb *cbp[]);
当一个IO控制块(struct iocb cb)初始化完毕,把这个指针放入一个数组中( cbs[0] = &cb),因为io_submit系统调用需要接受一个二维指针。在io_submit(ctx, 1, cbs)中, 参数分别为IO上下文(aio_context_t)、数组(struct iocb)大小、数组地址(cbs).
io_submit的返回值,可以是如下值:

A) ret = (提交的iocb的数目)
表示所有的iocb都被接受并处理

B) 0 < ret < (提交的iocb的数目)
io_submit() 系统调用会从传入的cbs中一个一个处理iocb,如果提交的某个iocb失败,将停止并且返回iocb的索引号。没办法知晓错 误的具体原因,但是如果第一个iocb提交失败,参看C条。
C) ret < 0
       有两种原因:
	1) 在io_submit()开始之前发生了某种错误(e.g.比如AIO context非法). 
2) 提交第一个iocb(cbx[0])失败

 3) 调用io_getevents()
   当提交了iocb之后,可以不用等待IO完成去做其他的操作。对于每一个已经完成的IO请求(成功或失败),内核都会创建一个io_event结构。io_getevent()系统调用可以用来获取这一结构。这需要做以下操作。
原型  int io_getevents(aio_context_t ctx, long min_nr, long nr, struct io_event *events, struct timespec *timeout)

a) 使用哪一个AIO上下文(变量ctx)
b) 内核把这个变量放入哪个内存位置 (变量events)
c) events的最小个数(变量min_nr,)

如果完成的iocb的个数比这个值要小io_getevents会阻塞,直到达到这个值,

参看第e条查看阻塞时间。

d) 想要获取的events的最大个数(变量nr)。

e) 如果获取不到足够的events,而又不想永久等待。可以指定相对时间(timeout)到最后一个参数,

如果timeout为NULL,表示永久等待。

如果timeout为0,io_getevents()不阻塞

编译并运行:

$ gcc -Wall submit_reslut.c -o submit_reslut -laio
$ ./submit_reslut
after io_setup ctx:1404534983884801
result:140735403362480
$ cat testfile
会发现文中中有相应的内容。

libaio用户空间函数
从上文可以看出,直接使用系统调用执行一个完整的IO输入输出,流程比较麻烦。在用户空间包装了几个函数用以简化这一操作。详细请参考

static inline void io_prep_pread(struct iocb *iocb, int fd, void *buf, size_t count, long long offset) static inline void io_prep_pwrite(struct iocb *iocb, int fd, void *buf, size_t count, long long offset) static inline void io_prep_preadv(struct iocb *iocb, int fd, const struct iovec *iov, int iovcnt, long long offset) static inline void io_prep_pwritev(struct iocb *iocb, int fd, const struct iovec *iov, int iovcnt, long long offset) static inline void io_prep_poll(struct iocb *iocb, int fd, int events) static inline void io_prep_fsync(struct iocb *iocb, int fd) static inline void io_prep_fdsync(struct iocb *iocb, int fd) static inline int io_poll(io_context_t ctx, struct iocb *iocb, io_callback_t cb, int fd, int events) static inline int io_fsync(io_context_t ctx, struct iocb *iocb, io_callback_t cb, int fd) static inline int io_fdsync(io_context_t ctx, struct iocb *iocb, io_callback_t cb, int fd) static inline void io_set_eventfd(struct iocb *iocb, int eventfd);


  评论这张
 
阅读(8700)| 评论(0)
推荐 转载

历史上的今天

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2017