自学内容网 自学内容网

Qemu virtio-blk 后端驱动开发 - PureFlash对接

本文以PureFlash为例,介绍了如何将一个新的存储类型对接到qemu虚拟化平台下,为虚机提供存储能力。

关于virtio-blk以及其工作原理这里就不介绍了,网上有很多分析的文章。总之就是如果我们想给虚机提供一种新的存储类型(不同于标准的块设备,文件,或者iSCSI等linux内置接口 ),我们就需要为qemu增加新的virtio-blk后端驱动来达到这个目的。典型的比如ceph rbd驱动

说到virtio协议,其核心是通过virtio queue在guest-host系统之间进行交互,这方面的资料目前还是比较多的。不过这部分对于我们开发后端驱动而言其实不需要触碰,qemu已经定义了一套更易理解的接口,实现方按部就班实现这些API接口就可以了。

virtio-blk后端接口有两种模式,旧的接口使用线程模式,新的接口使用协程模式。这里的新旧是指qemu的演进过程。这里我们使用协程模式。

一、virioblk IO接口

virtio-blk接口的关键是在自己的代码中定义并填充struct BlockDriver 结构体,通过这个结构体向qemu传递自己这个后端实现的信息以及API实现。我们先对这个结构体里的几个关键字段做下说明:

BlockDriver字段解释
const char *format_name驱动名称
int instance_size实例化一个块设备/虚拟盘,这个设备对象的大小(不是盘的大小,是表示这个盘的内存结构体大小)
const char *protocol_name在qemu启动的命令行上,通过<protocol>:filename 指定一个后端设备后,通过这个名字qemu才能知道对于一个块设备调用哪个驱动。通常这个名字设置成和format_name相同
 int GRAPH_UNLOCKED_PTR (*bdrv_file_open)(
        BlockDriverState *bs, QDict *options, int flags, Error **errp);
用于为qemu打开一个块设备,也就是当qemu需要创建一个新的块设备时首先调用这个函数进行打开操作
    void (*bdrv_parse_filename)(const char *filename, QDict *options, Error **errp);

把filename 字符串解析成方便使用的参数字典,即QDict类型的options。

通常qemu会接收一个复杂的字符串作为文件名,比如 "pfbd:test_v1", 其中pfbd是protocol_name, 后半部分是volume name。这个函数将解析后的结果保存到options中。后面这个options被传递给brv_file_open函数使用。

    int coroutine_fn GRAPH_RDLOCK_PTR (*bdrv_co_preadv)(BlockDriverState *bs,
        int64_t offset, int64_t bytes, QEMUIOVector *qiov,
        BdrvRequestFlags flags);

读操作函数,实现这个函数以提供读操作。

BlockDriver接口里定义了多个读操作函数,实现方只需要实现其中的一个即可。 

    int coroutine_fn GRAPH_RDLOCK_PTR (*bdrv_co_pwritev)(
        BlockDriverState *bs, int64_t offset, int64_t bytes, QEMUIOVector *qiov,
        BdrvRequestFlags flags);
写操作函数,和上面的读操作函数类似原理。

BlockDriver接口里定义了多个读写操作函数, 比如 bdrv_aio_preadv, bdrv_co_readv, bdrv_co_preadv, bdrv_co_preadv_part 都可以用来实现读操作,实现方根据自己的情况实现任意一个函数就可以。

这样就明确了,实现virtio-blk后端驱动的重点就是实现一个read函数和一个write函数。本文以实现bdrv_co_preadv、bdrv_co_pwritev为例。正如名字里的co暗示的,这两个函数以协程的模式工作,协程模型也是qemu新版本力推。

相比业界一些专门的协程库(比如 Argobots,...),qemu里的协程是一个非常轻量化的协程实现,只有创建协程,进入协程,出让执行权这几个操作。其他的甚至基本的锁操作都没有。

二、IO代码路径

当虚拟机里的guest OS执行IO操作时,首先把IO请求放入到virtio-queue里面,然后执行一个kick动作。这个kick动作就是一个对PCIe寄存器的写入操作:

但是虚拟设备是没有PCIe硬件存在的,这个写入操作引发VM_EXIT,进入到KVM的代码,KVM之后通过eventfd通知qemu,触发qemu的主事件循环里处理:

其中blk_aio_prwv的代码如下,这里创建IO协程:

我们编写的IO接口函数就是在这个协程里面被调用的:

三、接口实现

前面的IO路径已经跟到了pfbd_co_pwritev函数,作为接口实现的工作就是提供实现这一函数。

PureFlash项目提供了用户态的接口库libs5common.a , 这里提供了volume的打开,读写等操作函数。这些函数都是用户态API,用于对接qemu, TCMU( LIO的用户态后端实现,提供iSCSI接口), nbd, ViveNAS等访问。这些函数中用于进行读写操作函数是pf_io_submit/pf_iov_submit。

/**
 * Submit IO vector.
 * @param volume, the volume to submit IO
 * @param iov, io vector
 * @param iov_cnt, iovec count in param iov
 * @param length, total length of iovs
 * @param offset, offset in volume, of this IO to perform in volume
 * @param callback, the call back handler function to call when IO complete_status
 * @param cbk_arg, user defined data to passed to callback when IO complete. PureFlash system will not touch this argument
 * @param is_write, indict this IO is WRITE or READ. 1 for WRITE IO; 0 for READ IO
 * @return 0 on success, negative number to indicate submit error.
 * @retval -EINVAL  argument invalidate, length too large or offset not aligned on sector(512B)
 * @retval -EAGAIN  device is temporary busy, caller can retry this IO later
 *
 */
int pf_iov_submit(struct PfClientVolume* volume, const struct iovec *iov, const unsigned int iov_cnt, size_t length, off_t offset,
                  ulp_io_handler callback, void* cbk_arg, int is_write);

和大多数异步操作函数一样,pf_iov_submit的参数里包含了一个回调函数用于在IO完成时调用。但是Qemu的IO接口函数bdrv_co_preadv/bdrv_co_pwritev期望的行为是同步调用。因此接口实现时的一个重要功能就是实现异步/同步行为转换:

//这个函数在Qemu的IO线程中执行
static void pfbd_rw_cb_bh(void* opaque)
{
    PfbdCoData* data = opaque;
    data->complete=true;
    qemu_coroutine_enter(data->co); //唤醒处于等待中的IO 协程
}

//这个回调函数在PureFlash s5common库的事件循环线程中调用
static void pfbd_rw_cb(void* opaque, int ret)
{
    PfbdCoData* data = opaque;
    data->ret = ret;

    //调度一个IO后半段操作(Behand half)用于唤醒协程
    aio_bh_schedule_oneshot(bdrv_get_aio_context(data->bds),
        pfbd_rw_cb_bh, data);
}

static coroutine_fn int pfbd_co_preadv(BlockDriverState* bs,
    int64_t offset, int64_t bytes,
    QEMUIOVector* qiov, BdrvRequestFlags flags)
{
    int r;
    BDRVPfbdState* s = bs->opaque;
   
    PfbdCoData data = {
        .ret = -EINPROGRESS,
        .co= qemu_coroutine_self() ,
        .complete = false,
        .bds = bs,
    };
    
    // 调用PureFlash的API提交IO操作,传递异步操作回调函数 pfbd_rw_cb。
    // IO完成时 pfbd_rw_cb会被调用。可以看到这里不几乎不需要对其他参数做
    // 修改适配,因为虚拟机是PureFlash设计适用的重要场景,API的参数已经根
    // 据qemu接口做了尽可能适配
    r = pf_iov_submit(s->vol, qiov->iov, qiov->niov, bytes, offset, pfbd_rw_cb, &data, 0);
    if(r){
        error_report("submit IO error, rc:%d", r);
        return r;
    }

    //等待IO完成
    while (!data.complete) {
        qemu_coroutine_yield();  //出让协程CPU等待唤醒,因此这里不是忙等待
    }
    return data.ret;
}

通过上面的方法实现了IO路径,接下来要把驱动注册到qemu里


static const char *const qemu_pfbd_strong_runtime_opts[] = {
"volume",
"conf",
"snapshot",

NULL
};

static BlockDriver bdrv_pfbd = {
.format_name            = "pfbd",
.instance_size          = sizeof(BDRVPfbdState),
.bdrv_parse_filename    = qemu_pfbd_parse_filename,
.bdrv_file_open         = qemu_pfbd_open,
.bdrv_close             = qemu_pfbd_close,
        .bdrv_co_get_info       = qemu_pfbd_co_getinfo,
.create_opts            = &qemu_pfbd_create_opts,
.bdrv_co_getlength         = qemu_pfbd_co_getlength,
.protocol_name          = "pfbd",

        .bdrv_co_preadv = pfbd_co_preadv,
        .bdrv_co_pwritev = pfbd_co_pwritev,

.strong_runtime_opts    = qemu_pfbd_strong_runtime_opts,
};

static void bdrv_pfbd_init(void)
{
bdrv_register(&bdrv_pfbd);
}

block_init(bdrv_pfbd_init);

三、使用方法

使用下面的命令在启动qemu时使用PureFlash 卷

qemu/build//qemu-system-x86_64  -enable-kvm -cpu host -smp 1 -drive if=ide,file=./centos8.qcow2,cache=none \
        -drive if=virtio,file=pfbd:test_v1,cache=none \
        -m 4G -vnc :12 \
        -nic user,hostfwd=tcp::5555-:22

这个命令里面为qemu挂载了test_v1这个卷作为数据盘。

在虚机运行之前需要启动PureFlash集群(单个容器也可以提供服务),并创建名字为test_v1的卷,这个操作可以参考PureFlash存储系统介绍与上手指南-CSDN博客

本文里完整的代码: block/pfbd.c · cocalele/qemu - 码云 - 开源中国 (gitee.com)


原文地址:https://blog.csdn.net/winux/article/details/140025594

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!