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

news2024/9/22 5:30:07

本文以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)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1947032.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

解决显存不足问题:深度学习中的 Batch Size 调整【模型训练】

解决显存不足问题&#xff1a;深度学习中的 Batch Size 调整 在深度学习训练中&#xff0c;显存不足是一个常见的问题&#xff0c;特别是在笔记本等显存有限的设备上。本文将解释什么是 Batch Size&#xff0c;为什么调整 Batch Size 可以缓解显存不足的问题&#xff0c;以及调…

大数据-48 Redis 通信协议原理RESP 事件处理机制原理 文件事件 时间事件 Reactor多路复用

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; 目前已经更新到了&#xff1a; Hadoop&#xff08;已更完&#xff09;HDFS&#xff08;已更完&#xff09;MapReduce&#xff08;已更完&am…

鸿蒙开发仓颉语言【Hyperion: 一个支持自定义编解码器的TCP通信框架】组件

Hyperion: 一个支持自定义编解码器的TCP通信框架 特性 支持自定义编解码器高效的ByteBuffer实现&#xff0c;降低请求处理过程中数据拷贝自带连接池支持&#xff0c;支持连接重建、连接空闲超时易于扩展&#xff0c;可以积木式添加IoFilter处理入栈、出栈消息 组件 hyperio…

c++ 求解质因数

定义 这里先来了解几个定义&#xff08;如已了解&#xff0c;可直接看下一个板块&#xff09; 因数&#xff1a;又称为约数&#xff0c;如果整数a除以整数b&#xff08;b0&#xff09;的商正好是是整数而没有余数&#xff0c;我们就说b是a的因数 质数&#xff1a;又称为素数…

我在Vscode学Java泛型(泛型设计、擦除、通配符)

Java泛型 一、泛型 Generics的意义1.1 在没有泛型的时候&#xff0c;集合如何存储数据1.2 引入泛型的好处1.3 注意事项1.3.1 泛型不支持基本数据类型1.3.2 当泛型指定类型&#xff0c;传递数据时可传入该类及其子类类型1.3.3 如果不写泛型&#xff0c;类型默认是Object 二、泛型…

Python酷库之旅-第三方库Pandas(044)

目录 一、用法精讲 151、pandas.Series.any方法 151-1、语法 151-2、参数 151-3、功能 151-4、返回值 151-5、说明 151-6、用法 151-6-1、数据准备 151-6-2、代码示例 151-6-3、结果输出 152、pandas.Series.autocorr方法 152-1、语法 152-2、参数 152-3、功能 …

c++树(三)重心

目录 重心的基础概念 定义&#xff1a;使最大子树大小最小的点叫做树的重心 树的重心求解方式 例题&#xff1a; 重心的性质 性质1&#xff1a;重心点的最大子树大小不大于整棵树大小的一半。 性质1证明&#xff1a; 性质1的常用推导 推导1&#xff1a; 推导2&#x…

《Milvus Cloud向量数据库指南》——开源许可证的范围:深入解析与选择指南

在开源软件的广阔天地中,开源许可证作为连接开发者与用户之间的重要法律桥梁,其类型多样且各具特色。每一种许可证都精心设计了特定的权限、限制和要求,旨在保护创作者的权益,同时促进软件的创新与共享。对于开发者和用户而言,深入理解并恰当选择适合的开源许可证,是确保…

C++树(四)二叉树

目录 二叉树的定义&#xff1a; 二叉树相关术语&#xff1a; 二叉树的概念与性质 二叉树基本性质 二叉树的节点数量 满二叉树概念&#xff1a; 完全二叉树概念&#xff1a; 完全二叉树性质&#xff1a; 二叉树的存储 二叉树的遍历 在此基础上&#xff0c;二叉树的遍历…

mac下010editor的配置文件路径

1.打开访达&#xff0c;点击前往&#xff0c;输入~/.config 2.打开这个文件夹 把里面的 010 Editor.ini 文件删除即可&#xff0c;重新安装010 Editor即可

有没有下面符合以下条件的电子时钟的代码

&#x1f3c6;本文收录于《CSDN问答解答》专栏&#xff0c;主要记录项目实战过程中的Bug之前因后果及提供真实有效的解决方案&#xff0c;希望能够助你一臂之力&#xff0c;帮你早日登顶实现财富自由&#x1f680;&#xff1b;同时&#xff0c;欢迎大家关注&&收藏&…

【React1】React概述、基本使用、脚手架、JSX、组件

文章目录 1. React基础1.1 React 概述1.1.1 什么是React1.1.2 React 的特点声明式基于组件学习一次,随处使用1.2 React 的基本使用1.2.1 React的安装1.2.2 React的使用1.2.3 React常用方法说明React.createElement()ReactDOM.render()1.3 React 脚手架的使用1.3.1 React 脚手架…

PostgreSQL使用(四)——数据查询

说明&#xff1a;对于一门SQL语言&#xff0c;数据查询是我们非常常用的&#xff0c;也是SQL语言中非常大的一块。本文介绍PostgreSQL使用中的数据查询&#xff0c;如有一张表&#xff0c;内容如下&#xff1a; 简单查询 --- 1.查询某张表的全部数据 select * from tb_student…

MSPM0G3507基于keil无法烧录的解决方法

在学习M0的板卡过程中&#xff0c;遇到了诸多玄学问题。网上的教学大多基于CCS开发&#xff0c;对keil的教学几乎没有。 一开始我以为这个问题是没添加这个&#xff0c;但其实并非如此 在群里的网友说的清除flash&#xff0c;插拔USB,这些都不管用,后面也发现先在CCS烧录一遍&…

前端开发知识(二)-css

<head> <style> div{ } </style> </head> div是布局标签&#xff0c; 一般放在head标签内&#xff0c;最下部。 若直接在在.css文件中写css,文件中&#xff0c;直接写就行&#xff0c;如下所示。 div{ }

VLLM代码解读 | VLLM Hack 3

在上一期&#xff0c;我们看到了多个输入如何被封装&#xff0c;然后被塞入llm_engine中&#xff0c;接下来&#xff0c;通过_run_engine,我们要进行输入的处理了。 def _run_engine(self, *, use_tqdm: bool) -> List[Union[RequestOutput, EmbeddingRequestOutput]]:# Ini…

java-poi实现excel自定义注解生成数据并导出

因为项目很多地方需要使用导出数据excel的功能&#xff0c;所以开发了一个简易的统一生成导出方法。 依赖 <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi-ooxml</artifactId> <version>4.0.1</version…

【LeetCode】201. 数字范围按位与

1. 题目 2. 分析 这题挺难想的&#xff0c;我到现在还没想明白&#xff0c;为啥只用左区间和右区间就能找到目标值了&#xff0c;而不用挨个做与操作&#xff1f; 3. 代码 class Solution:def rangeBitwiseAnd(self, left: int, right: int) -> int:left_bin bin(left).…

五. TensorRT API的基本使用-TensorRT-network-structure

目录 前言0. 简述1. 案例运行2. 代码分析2.1 main.cpp2.2 model.cpp 总结下载链接参考 前言 自动驾驶之心推出的 《CUDA与TensorRT部署实战课程》&#xff0c;链接。记录下个人学习笔记&#xff0c;仅供自己参考 本次课程我们来学习课程第五章—TensorRT API 的基本使用&#x…

java面向对象进阶进阶篇--《接口和接口与抽象类综合案例》(附带全套源代码)

个人主页→VON 收录专栏→java从入门到起飞 抽象类→抽象类和抽象方法 目录 一、初识接口 特点和用途 示例&#xff1a; Animal类 Dog类 Frog类 Rabbit类 Swim接口 text测试类 结果展示&#xff1a; 二、接口的细节 接口中的成员特点&#xff1a; 成员特点与接口的关…