kvm随笔系列二: Qemu中的AIO小结

(1) block驱动使用了Linux AIO机制

异步非阻塞IO称为:AIO.

linux下有aio封装,aio_*系列的调用是glibc提供的,是glibc用线程+阻塞调用来模拟的,性能很差,为了能更多的控制io行为,可以使用更为低级libaio。
libaio项目: http://oss.oracle.com/projects/libaio-oracle/

libaio的使用并不复杂,过程为:libaio的初始化,io请求的下发和回收,libaio销毁。


一、libaio接口
libaio提供下面五个主要API函数:

int io_setup(int maxevents, io_context_t *ctxp);
int io_destroy(io_context_t ctx);
int io_submit(io_context_t ctx, long nr, struct iocb *ios[]);
int io_cancel(io_context_t ctx, struct iocb *iocb, struct io_event *evt);
int io_getevents(io_context_t ctx_id, long min_nr, long nr, struct io_event *events, struct timespec *timeout);

五个宏定义:

void io_set_callback(struct iocb *iocb, io_callback_t cb);
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);

这五个宏定义都是操作struct iocb的结构体。struct iocb是libaio中很重要的一个结构体,用于表示IO,但是其结构略显复杂,为了保持封装性不建议直接操作其元素而用上面五个宏定义操作。

二、libaio的初始化和销毁

观察libaio五个主要API,都用到类型为io_context的变量,这个变量为libaio的工作空间。不用具体去了解这个变量的结构,只需要了解其相关操作。创建和销毁libaio分别用到io_setup(也可以用io_queue_init,区别只是名字不一样而已)和io_destroy。

int io_setup(int maxevents, io_context_t *ctxp);
int io_destroy(io_context_t ctx);

三、libaio读写请求的下发和回收

1. 请求下发

libaio的读写请求都用io_submit下发。下发前通过io_prep_pwrite和io_prep_pread生成iocb的结构体,做为io_submit的参数。这个结构体中指定了读写类型、起始扇区、长度和设备标志符。

libaio的初始化不是针对一个具体设备进行初始,而是创建一个libaio的工作环境。读写请求下发到哪个设备是通过open函数打开的设备标志符指定。


2. 请求返回

读写请求下发之后,使用io_getevents函数等待io结束信号:

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

io_getevents返回events的数组,其参数events为数组首地址,nr为数组长度(即最大返回的event数),min_nr为最少返回的events数。timeout可填NULL表示无等待超时。io_event结构体的声明为:

struct io_event {
    PADDEDptr(void *data, __pad1);
    PADDEDptr(struct iocb *obj,  __pad2);
    PADDEDul(res,  __pad3);
    PADDEDul(res2, __pad4);
};

其中,res为实际完成的字节数;res2为读写成功状态,0表示成功;obj为之前下发的struct iocb结构体。这里有必要了解一下struct iocb这个结构体的主要内容:

iocbp->iocb.u.c.nbytes 字节数
iocbp->iocb.u.c.offset 偏移
iocbp->iocb.u.c.buf 缓冲空间
iocbp->iocb.u.c.flags 读写

3. 自定义字段

struct iocb除了自带的元素外,还留有供用户自定义的元素,包括回调函数和void *的data指针。如果在请求下发前用io_set_callback绑定用户自定义的回调函数,那么请求返回后就可以显示的调用该函数。回调函数的类型为:

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

另外,还可以通过iocbp->data指针挂上用户自己的数据。

注意:实际使用中发现回调函数和data指针不能同时用,可能回调函数本身就是使用的data指针。


下面是qemu的使用API封装:

void *laio_init(void)
{
    struct qemu_laio_state *s;


    s = g_malloc0(sizeof(*s));
    s->efd = eventfd(0, 0);
    if (s->efd == -1)
        goto out_free_state;
    fcntl(s->efd, F_SETFL, O_NONBLOCK);


    if (io_setup(MAX_EVENTS, &s->ctx) != 0) //io setup 注册了io context s->ctx
        goto out_close_efd;


    qemu_aio_set_fd_handler(s->efd, qemu_laio_completion_cb, NULL,
        qemu_laio_flush_cb, s);  //注册一个完成完成回调与flush回调



    return s;

 .......
}

BlockDriverAIOCB *laio_submit(BlockDriverState *bs, void *aio_ctx, int fd,
        int64_t sector_num, QEMUIOVector *qiov, int nb_sectors,
        BlockDriverCompletionFunc *cb, void *opaque, int type)
{
    struct qemu_laio_state *s = aio_ctx;
    struct qemu_laiocb *laiocb;
    struct iocb *iocbs;
    off_t offset = sector_num * 512;


    laiocb = qemu_aio_get(&laio_pool, bs, cb, opaque);
    laiocb->nbytes = nb_sectors * 512;
    laiocb->ctx = s;
    laiocb->ret = -EINPROGRESS;
    laiocb->is_read = (type == QEMU_AIO_READ);
    laiocb->qiov = qiov;


    iocbs = &laiocb->iocb;

   //根据读写,用文件句柄,和读写iov初始化iocbs
    switch (type) {
    case QEMU_AIO_WRITE:
        io_prep_pwritev(iocbs, fd, qiov->iov, qiov->niov, offset); 
break;
    case QEMU_AIO_READ:
        io_prep_preadv(iocbs, fd, qiov->iov, qiov->niov, offset);
break;
    /* Currently Linux kernel does not support other operations */
    default:
        goto out_free_aiocb;
    }

   //将iocbs与laio_init中分配好的efd关联
    io_set_eventfd(&laiocb->iocb, s->efd);

    s->count++;


    if (io_submit(s->ctx, 1, &iocbs) < 0) //提交io ctx
        goto out_dec_count;
    return &laiocb->common;

  ........
}


static void qemu_laio_completion_cb(void *opaque)
{
    struct qemu_laio_state *s = opaque;


    while (1) {
        struct io_event events[MAX_EVENTS];
        uint64_t val;
        ssize_t ret;
        struct timespec ts = { 0 };
        int nevents, i;


        do {
            ret = read(s->efd, &val, sizeof(val));
        } while (ret == -1 && errno == EINTR);


        if (ret == -1 && errno == EAGAIN)
            break;


        if (ret != 8)
            break;


        do {
            nevents = io_getevents(s->ctx, val, MAX_EVENTS, events, &ts); //nevents为完成io数
        } while (nevents == -EINTR);


        for (i = 0; i < nevents; i++) {
            struct iocb *iocb = events[i].obj;
            struct qemu_laiocb *laiocb =
                    container_of(iocb, struct qemu_laiocb, iocb);


            laiocb->ret = io_event_ret(&events[i]); //取到io返回值
            qemu_laio_process_completion(s, laiocb);//调用上层完成回调

        }
    }
}


注意使用aio, 在打开文件时要使用O_DIRECT

raw_open_common ==》 

    raw_parse_flags(bdrv_flags, &s->open_flags);
    s->fd = -1;
    fd = qemu_open(filename, s->open_flags, 0644);

(2) Qemu的 bottom half api示例

    与内核bottom half机制类似, qemu也提供了该机制    

    bm->bh = qemu_bh_new(bmdma_restart_bh, &bm->dma); //注册处理函数
        qemu_bh_schedule(bm->bh); //通知bottom half调度执行

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值