vduse: VDUSE(vDPA Device in Userspace) 用户态vdpa设备
VDUSE 简介:virtio 软件定义的数据路径
2022 年 7 月 14 日 谢永吉, 王杰森
标签: 存储 虚拟化
用户空间中的 vDPA 设备 ( VDUSE ) 是一种为虚拟机 (VM) 和容器工作负载提供软件定义存储和网络服务的新兴方法,vDPA(virtio 数据路径加速)内核子系统是 VDUSE 背后的引擎。不熟悉 vDPA 内核框架,请参阅我们的vDPA 内核框架和用于内核子系统交互的 vDPA 总线驱动程序简介博客,以熟悉vDPA 总线、vDPA 总线驱动程序和vDPA 设备等概念,因为我们假设读者很熟悉以及本博客中的这些主题。
简而言之,VDUSE 使您能够在用户空间中轻松实施软件模拟的 vDPA 设备,以服务虚拟机和容器工作负载。
vDPA 最初是为了帮助在专用硬件(例如 smartNIC)中实现virtio(一种开放标准控制和数据平面)而开发的,这只需要在硬件中支持 virtio 数据平面,并使用 vDPA 将供应商特定的控制平面转换为 virtio 控制平面。(显着简化供应商的工作)。
VDUSE 已发展为提供基于软件的 vDPA 设备(相对于之前的硬件 vDPA 设备),该设备可以利用 vDPA 内核子系统为虚拟机和容器工作负载提供标准接口,这对于优化的用户空间应用程序(例如存储性能开发)非常有用。套件 (SPDK) 和数据平面开发套件 (DPDK) 应用程序需要高效的接口来连接到计算机上运行的所有工作负载(虚拟机和容器)。
与硬件 vDPA 实现相比,vDPA 用户空间设备具有以下优点:
本博客介绍了 VDUSE 架构,并回顾了几个演示其用法的示例。
VDUSE的基础设施包括两个关键块:位于用户空间的VDUSE守护进程和位于内核的VDUSE模块。
图 1:VDUSE 内核模块和用户空间守护进程
VDUSE 守护进程负责实现用户空间 vDPA 设备,它包含设备模拟和 virtio 数据平面。
内核中的VDUSE模块负责桥接VDUSE守护进程和vDPA框架,以便用户空间vDPA设备可以在vDPA框架下工作,它包含三个功能模块:
VDUSE 容器支持的关键点是用户空间 vDPA 设备所附加的 vDPA 总线驱动程序。目前,vDPA 内核框架支持两种类型的 vDPA 总线驱动程序:virtio-vdpa(用于容器)和vhost-vdpa(用于虚拟机, 如QEMU中的GUEST)
如果您想通过 VDUSE 为容器工作负载提供接口,则 vDPA 设备应与 virtio-vdpa 绑定(如下所示)。
图 2:通过 VDUSE 提供容器工作负载
在这种情况下,virtio-vDPA 总线驱动程序提供了一个 virtio 设备,可以将各种内核子系统连接到该 virtio 设备以供用户空间应用程序使用。
如前所述,为了使用户空间 VDUSE 守护进程能够访问 virtio 设备驱动程序中的数据缓冲区,在数据平面的 VDUSE 内核模块中引入了具有反弹缓冲机制的基于 MMU 的软件 IOTLB。
数据从内核空间中的原始数据缓冲区复制到反弹缓冲区并返回,具体取决于传输方向,然后用户空间守护程序只需将反弹缓冲区映射到其地址空间,而不是原始地址空间,这可能会发生。在同一页中包含其他私有内核数据。
如果 vDPA 设备与 vhost-vdpa 绑定,则 VDUSE 守护程序可以为虚拟机工作负载提供服务,如下所示。
图 3:通过 VDUSE 提供虚拟机工作负载
在这种情况下,虚拟主机 (vhost) 设备由 vhost-vDPA 总线驱动程序提供,因此它可以用作在 VM 内运行的 virtio 驱动程序的 vhost 后端。
共享内存机制: 在数据平面中,VM 的内存将与 VDUSE 守护进程共享,这样,VDUSE 守护进程可以直接访问驻留在用户空间内存区域中的数据缓冲区,而无需依赖反弹缓冲机制。
现在您已经熟悉了 VDUSE 如何连接到容器和虚拟机工作负载,接下来看一下为这两种工作负载类型提供服务的整体解决方案。
图4:VDUSE架构概览
在图 4 中,核心组件 — VDUSE 守护进程(用户空间)和 VDUSE 模块(内核) — 用红线勾勒出轮廓。
VDUSE 用户空间守护程序可以通过将 VDUSE 内核模块创建的 vDPA 设备绑定到不同的 vDPA 总线驱动程序,为容器或虚拟机工作负载提供软件定义的存储和网络服务。
以下用例演示了VDUSE 有价值的两种示例。
计算和存储分离的架构意味着您通常需要一种从计算节点中的虚拟机和容器访问远程存储服务的方法,VDUSE 为这种情况实现了可靠的解决方案。
图 5:用于远程存储访问的 VDUSE 解决方案
VDUSE存储守护进程是整个解决方案的核心组件,它使用VDUSE框架来模拟vDPA块设备,然后通过网络将来自VM或容器的I/O请求转发到远程存储。
与其他解决方案相比,VDUSE 方法:
您还可以使用 VDUSE 启用专注于 VM 工作负载的现有 SPDK 应用程序(使用虚拟主机用户界面),为容器工作负载提供相同的服务,图 6 显示了其工作原理。
图 6:为容器重用 vhost-user 解决方案
上面介绍了一个 VDUSE Agent 代理来桥接容器和 SPDK 守护进程,一方面,它使用 VDUSE 框架来模拟绑定到 virtio-vdpa 总线驱动程序的 vDPA 块设备,另一方面,它充当 vhost用户客户端(vhost-user-client)与 SPDK 守护程序中的 vhost-user-server进行通信。
通过 VDUSE 框架,VDUSE 代理可以获取 virtio-blk 设备驱动程序数据平面中使用的内存区域(包括可用环、已用环、描述符表和包含 virtio 请求数据的反弹缓冲区)。
然后,通过 vhost-user 协议,VDUSE 代理可以将它们传输到 SPDK 守护程序,因此,当现有 SPDK 数据平面遵循 virtio 规范访问这些内存区域时,它会访问内核 virtio-blk 设备驱动程序中的数据。在此流程中,VDUSE 模块负责将数据复制到反弹缓冲区或从反弹缓冲区复制数据(MMU和软件IOTLB机制)。
软件工程师,字节跳动
YongJi Xie 是字节跳动的软件工程师,致力于 QEMU 和 Linux 内核中的 I/O 虚拟化主题。
首席软件工程师
经验丰富的 Red Hat 高级软件工程师,具有在计算机软件行业工作的经验。Linux virtio、vhost 和 vdpa 驱动程序的共同维护者。
阅读完整的简历
字节跳动:bytedance, vduse, commit: https://github.com/ssbandjl/linux/commit/c8a6153b6c59d95c0e091f053f6f180952ade91e
kernel_doc: https://docs.kernel.org/userspace-api/vduse.html
vduse:在用户空间引入 VDUSE - vDPA 设备,此 VDUSE 驱动程序支持在用户空间实现软件模拟的 vDPA 设备。vDPA 设备由 /dev/vduse/control 上的 ioctl(VDUSE_CREATE_DEV) 创建。然后将字符设备接口 (/dev/vduse/$NAME) 导出到用户空间进行设备模拟。为了使设备模拟更安全,设备的控制路径在内核中处理。引入了一种消息机制来将一些数据平面相关的控制消息转发到用户空间。并且在数据路径中,DMA 缓冲区将通过不同的方式映射到用户空间地址空间,具体取决于 vDPA 设备所连接的 vDPA 总线。在 virtio-vdpa 情况下,使用基于 MMU 的软件 IOTLB 来实现这一点。在 vhost-vdpa 情况下,DMA 缓冲区位于用户空间内存区域中,可以通过传输 shmfd 将其共享给 VDUSE 用户空间进程。有关 VDUSE 设计和使用的更多详细信息,请参阅后续文档提交
modprobe vduse
insmod vduse.ko
drivers/vdpa/vdpa_user/vduse_dev.c
module_init(vduse_init)
class_register(&vduse_class)
.name = "vduse"
"vduse/%s"
alloc_chrdev_region(&vduse_major, 0, VDUSE_DEV_MAX, "vduse")
cdev_init(&vduse_ctrl_cdev, &vduse_ctrl_fops)
cdev_add(&vduse_ctrl_cdev, vduse_major, 1)
dev = device_create(&vduse_class, NULL, vduse_major, NULL, "control") -> /dev/vduse/control
cdev_init(&vduse_cdev, &vduse_dev_fops) -> /dev/vduse/$DEVICE
cdev_add(&vduse_cdev, MKDEV(MAJOR(vduse_major), 1), VDUSE_DEV_MAX - 1)
vduse_irq_wq = alloc_workqueue("vduse-irq", WQ_HIGHPRI | WQ_SYSFS | WQ_UNBOUND, 0)
vduse_irq_bound_wq = alloc_workqueue("vduse-irq-bound", WQ_HIGHPRI, 0)
vduse_domain_init
iova_cache_get() -> 内核启动时,通过该函数,声明了 struct iova 结构体专用的slab分配器
iova_cache = kmem_cache_create("iommu_iova", sizeof(struct iova), 0, SLAB_HWCACHE_ALIGN, NULL)
vduse_mgmtdev_init
vduse_mgmt->mgmt_dev.ops = &vdpa_dev_mgmtdev_ops
vdpa_mgmtdev_register(&vduse_mgmt->mgmt_dev)
static const struct vdpa_mgmtdev_ops vdpa_dev_mgmtdev_ops = {
.dev_add = vdpa_dev_add,
dev = vduse_find_dev(name)
idr_for_each_entry(&vduse_idr, dev, id) <- static DEFINE_IDR(vduse_idr
vduse_dev_init_vdpa(dev, name)
vdev = vdpa_alloc_device(struct vduse_vdpa, vdpa, dev->dev, &vduse_vdpa_config_ops, 1, 1, name, true)
set_dma_ops(&vdev->vdpa.dev, &vduse_dev_dma_ops)
dev->domain = vduse_domain_create(VDUSE_IOVA_SIZE - 1, dev->bounce_size)
domain->iotlb = vhost_iotlb_alloc(0, 0)
file = anon_inode_getfile("[vduse-domain]", &vduse_domain_fops, domain, O_RDWR)
init_iova_domain(&domain->stream_iovad,PAGE_SIZE, IOVA_START_PFN)
iova_domain_init_rcaches(&domain->stream_iovad)
_vdpa_register_device(&dev->vdev->vdpa, dev->vq_num) -> __vdpa_register_device(vdev, nvqs)
dev = bus_find_device(&vdpa_bus, NULL, dev_name(&vdev->dev), vdpa_name_match)
device_add(&vdev->dev)
.dev_del = vdpa_dev_del,
};
static const struct dma_map_ops vduse_dev_dma_ops = {
.map_page = vduse_dev_map_page,
vduse_domain_map_page(domain, page, offset, size, dir, attrs)
dma_addr_t iova = vduse_domain_alloc_iova(iovad, size, limit)
iova_pfn = alloc_iova_fast(iovad, iova_len, limit >> shift, true) -> allocates an iova from rcache -> 首先尝试从缓存中查找满足条件的 I/O 虚拟地址空间地址段,如果找到就成功返回;否则,调用 alloc_iova() 函数分配 IOVA 内存段,如果需要刷新缓存,则释放 CPU 缓存的所有 IOVA 范围
iova_pfn = iova_rcache_get
new_iova = alloc_iova(iovad, size, limit_pfn, true)
return (dma_addr_t)iova_pfn << shift
vduse_domain_init_bounce_map
vduse_iotlb_add_range(domain, 0, domain->bounce_size - 1, 0, VHOST_MAP_RW, domain->file, 0)
map_file->file = get_file(file)
vhost_iotlb_add_range_ctx
vduse_domain_map_bounce_page
map->bounce_page = alloc_page(GFP_ATOMIC)
vduse_domain_bounce
.unmap_page = vduse_dev_unmap_page,
.alloc = vduse_dev_alloc_coherent,
addr = vduse_domain_alloc_coherent(domain, size, (dma_addr_t *)&iova, flag, attrs)
dma_addr_t iova = vduse_domain_alloc_iova(iovad, size, limit)
alloc_pages_exact -> 分配精确数量的物理连续页面
vduse_iotlb_add_range virt_to_phys
*dma_addr = (dma_addr_t)iova
.free = vduse_dev_free_coherent,
.max_mapping_size = vduse_dev_max_mapping_size,
};
static const struct file_operations vduse_domain_fops = {
.owner = THIS_MODULE,
.mmap = vduse_domain_mmap,
.release = vduse_domain_release,
};
static const struct vdpa_config_ops vduse_vdpa_config_ops = {
.set_vq_address= vduse_vdpa_set_vq_address,
vq->desc_addr = desc_area;
vq->driver_addr = driver_area;
vq->device_addr = device_area;
.kick_vq= vduse_vdpa_kick_vq,
schedule_work(&vq->kick)
vduse_vq_kick(vq)
.set_vq_cb= vduse_vdpa_set_vq_cb,
.set_vq_num = vduse_vdpa_set_vq_num,
.set_vq_ready= vduse_vdpa_set_vq_ready,
.get_vq_ready= vduse_vdpa_get_vq_ready,
.set_vq_state= vduse_vdpa_set_vq_state,
.get_vq_state= vduse_vdpa_get_vq_state,
.get_vq_align= vduse_vdpa_get_vq_align,
.get_device_features= vduse_vdpa_get_device_features,
.set_driver_features= vduse_vdpa_set_driver_features,
.get_driver_features= vduse_vdpa_get_driver_features,
.set_config_cb= vduse_vdpa_set_config_cb,
.get_vq_num_max= vduse_vdpa_get_vq_num_max,
.get_device_id= vduse_vdpa_get_device_id,
.get_vendor_id= vduse_vdpa_get_vendor_id,
.get_status= vduse_vdpa_get_status,
.set_status= vduse_vdpa_set_status,
.get_config_size= vduse_vdpa_get_config_size,
.get_config= vduse_vdpa_get_config,
.set_config= vduse_vdpa_set_config,
.get_generation= vduse_vdpa_get_generation,
.set_vq_affinity= vduse_vdpa_set_vq_affinity,
if (cpu_mask)
cpumask_copy(&dev->vqs[idx]->irq_affinity, cpu_mask)
else
cpumask_setall(&dev->vqs[idx]->irq_affinity)
.get_vq_affinity= vduse_vdpa_get_vq_affinity,
.reset= vduse_vdpa_reset,
.set_map= vduse_vdpa_set_map,
.free= vduse_vdpa_free,
};
控制设备回调
static const struct file_operations vduse_ctrl_fops = {
.owner= THIS_MODULE,
.open= vduse_open,
control = kmalloc(sizeof(struct vduse_control), GFP_KERNEL)
file->private_data = control
.release= vduse_release,
.unlocked_ioctl= vduse_ioctl,
switch (cmd)
case VDUSE_CREATE_DEV -> #define VDUSE_CREATE_DEV_IOW(VDUSE_BASE, 0x02, struct vduse_dev_config)
vduse_create_dev(&config, buf, control->api_version)
dev = vduse_dev_create()
INIT_LIST_HEAD(&dev->send_list)
INIT_LIST_HEAD(&dev->recv_list)
INIT_WORK(&dev->inject, vduse_dev_irq_inject)
dev->config_cb.callback(dev->config_cb.private)
dev->device_id = config->device_id <- from qemu -> vblk_exp->dev = vduse_dev_create(vblk_opts->name, VIRTIO_ID_BLOCK, 0,features, num_queues,sizeof(struct virtio_blk_config),(char *)&config, &vduse_blk_ops,vblk_exp)
dev->bounce_size = VDUSE_BOUNCE_SIZE -> 64MB
idr_alloc(&vduse_idr, dev, 1, VDUSE_DEV_MAX, GFP_KERNEL)
dev->dev = device_create_with_groups(&vduse_class, NULL, MKDEV(MAJOR(vduse_major), dev->minor), dev, vduse_dev_groups, "%s", config->name) -> new kernel -> creates a device and registers it with sysfs
vduse_dev_init_vqs(dev, config->vq_align, config->vq_num)
dev->vqs = kcalloc(dev->vq_num, sizeof(*dev->vqs), GFP_KERNEL)
for (i = 0; i < vq_num; i++)
dev->vqs[i]->irq_effective_cpu = IRQ_UNBOUND
INIT_WORK(&dev->vqs[i]->inject, vduse_vq_irq_inject)
vq->cb.callback(vq->cb.private)
INIT_WORK(&dev->vqs[i]->kick, vduse_vq_kick_work) -> vduse_vq_kick(vq)
if (vq->kickfd)
eventfd_signal(vq->kickfd) -> vq->kickfd = ctx <- vduse_kickfd_setup
-> to qemu -> on_vduse_vq_kick ->通知QEMU进行处理
else
vq->kicked = true
kobject_add(&dev->vqs[i]->kobj, &dev->dev->kobj, "vq%d", i)
__module_get(THIS_MODULE) -> 增加模块的引用计数
case VDUSE_DESTROY_DEV
name[VDUSE_NAME_MAX - 1] = '\0'
vduse_destroy_dev(name)
vduse_dev_reset(dev)
device_destroy(&vduse_class, MKDEV(MAJOR(vduse_major), dev->minor))
idr_remove(&vduse_idr, dev->minor)
vduse_dev_deinit_vqs(dev)
vduse_domain_destroy(dev->domain)
module_put(THIS_MODULE)
.compat_ioctl= compat_ptr_ioctl,
.llseek= noop_llseek,
};
// vduse设备回调函数
static const struct file_operations vduse_dev_fops = {
.owner= THIS_MODULE,
.open= vduse_dev_open,
.release= vduse_dev_release,
.read_iter= vduse_dev_read_iter,
.write_iter= vduse_dev_write_iter,
.poll= vduse_dev_poll,
.unlocked_ioctl= vduse_dev_ioctl,
case VDUSE_IOTLB_GET_FD
map = vhost_iotlb_itree_first(dev->domain->iotlb, entry.start, entry.last)
receive_fd(f, NULL, perm_to_file_flags(entry.perm))
case VDUSE_DEV_GET_FEATURES
ret = put_user(dev->driver_features, (u64 __user *)argp)
case VDUSE_DEV_SET_CONFIG
copy_from_user(dev->config + config.offset, argp + size, config.length)
case VDUSE_DEV_INJECT_CONFIG_IRQ
vduse_dev_queue_irq_work(dev, &dev->inject, IRQ_UNBOUND)
case VDUSE_VQ_SETUP
index = array_index_nospec(config.index, dev->vq_num)
dev->vqs[index]->num_max = config.max_size
case VDUSE_VQ_GET_INFO
vq = dev->vqs[index];
vq_info.desc_addr = vq->desc_addr;
vq_info.driver_addr = vq->driver_addr;
vq_info.device_addr = vq->device_addr;
vq_info.num = vq->num;
case VDUSE_VQ_SETUP_KICKFD
vduse_kickfd_setup(dev, &eventfd)
ctx = eventfd_ctx_fdget(eventfd->fd)
vq->kickfd = ctx
case VDUSE_VQ_INJECT_IRQ
vduse_dev_queue_irq_work(dev, &dev->vqs[index]->inject,dev->vqs[index]->irq_effective_cpu)
queue_work(vduse_irq_wq, irq_work)
or queue_work_on(irq_effective_cpu, vduse_irq_bound_wq, irq_work)
case VDUSE_IOTLB_REG_UMEM
vduse_dev_reg_umem(dev, umem.iova, umem.uaddr, umem.size)
npages = size >> PAGE_SHIFT;
page_list = __vmalloc(array_size(npages, sizeof(struct page *)), GFP_KERNEL_ACCOUNT);
umem = kzalloc(sizeof(*umem), GFP_KERNEL)
pinned = pin_user_pages(uaddr, npages, FOLL_LONGTERM | FOLL_WRITE, page_list) -> pin user pages in memory for use by other devices
vduse_domain_add_user_bounce_pages(dev->domain, page_list, pinned)
memcpy_to_page(pages[i], 0, page_address(map->bounce_page), PAGE_SIZE)
mmgrab(current->mm)
case VDUSE_IOTLB_DEREG_UMEM
vduse_dev_dereg_umem(dev, umem.iova, umem.size)
case VDUSE_IOTLB_GET_INFO
map = vhost_iotlb_itree_first(dev->domain->iotlb, info.start, info.last)
.compat_ioctl= compat_ptr_ioctl,
.llseek= noop_llseek,
};
# launch QSD exposing the VM image as `vduse1` vDPA device 通过vduse-blk将qcow2导出为用户态块设备
$ qemu-storage-daemon \
--blockdev file,filename=Fedora-Cloud-Base-39-1.5.x86_64.qcow2,node-name=file \
--blockdev qcow2,file=file,node-name=qcow2 \
--export vduse-blk,id=vduse1,name=vduse1,num-queues=1,node-name=qcow2,writable=on &
storage-daemon/qemu-storage-daemon.c
int main(int argc, char *argv[])
qemu_init_exec_dir(argv[0])
os_setup_signal_handling()
process_options(argc, argv, true) <- --export vduse-blk,id=vduse1,name=vduse1,num-queues=1,node-name=qcow2,writable=on
case OPTION_EXPORT
qmp_block_export_add
blk_exp_add
blk_exp_find_driver
for (i = 0; i < ARRAY_SIZE(blk_exp_drivers); i++)
bdrv_init
init_qmp_commands
qemu_init_main_loop(&error_fatal)
process_options(argc, argv, false)
pid_file_init
os_setup_post
main_loop_wait(false)
blk_exp_close_all
static const BlockExportDriver *blk_exp_drivers[] = {
&blk_exp_nbd,
#ifdef CONFIG_VHOST_USER_BLK_SERVER
&blk_exp_vhost_user_blk,
#endif
#ifdef CONFIG_FUSE
&blk_exp_fuse,
#endif
#ifdef CONFIG_VDUSE_BLK_EXPORT
&blk_exp_vduse_blk,
#endif
};
const BlockExportDriver blk_exp_vduse_blk = {
.type = BLOCK_EXPORT_TYPE_VDUSE_BLK,
.instance_size = sizeof(VduseBlkExport),
.create = vduse_blk_exp_create,
config.capacity = cpu_to_le64(blk_getlength(exp->blk) >> VIRTIO_BLK_SECTOR_BITS)
vblk_exp->dev = vduse_dev_create(vblk_opts->name, VIRTIO_ID_BLOCK, 0,features, num_queues,sizeof(struct virtio_blk_config),(char *)&config, &vduse_blk_ops,vblk_exp) -> 用户态QEMU创建块设备
subprojects/libvduse/libvduse.c
ioctl(ctrl_fd, VDUSE_CREATE_DEV, dev_config)
vduse_dev_setup_queue(vblk_exp->dev, i, queue_size)
aio_set_fd_handler(exp->ctx, vduse_dev_get_fd(vblk_exp->dev), on_vduse_dev_kick, NULL, NULL, NULL, vblk_exp->dev)
blk_add_aio_context_notifier(exp->blk, blk_aio_attached, blk_aio_detach, vblk_exp)
blk_set_dev_ops(exp->blk, &vduse_block_ops, exp)
blk_set_disable_request_queuing(exp->blk, true)
.delete = vduse_blk_exp_delete,
.request_shutdown = vduse_blk_exp_request_shutdown,
};
static const VduseOps vduse_blk_ops = {
.enable_queue = vduse_blk_enable_queue,
aio_set_fd_handler(vblk_exp->export.ctx, vduse_queue_get_fd(vq), on_vduse_vq_kick, NULL, NULL, NULL, vq)
on_vduse_vq_kick -> QEMU收到VQ内核通知,执行IO处理函数
vduse_blk_vq_handler(dev, vq)
while (1)
vduse_queue_pop(vq, sizeof(VduseBlkReq))
Coroutine *co = qemu_coroutine_create(vduse_blk_virtio_process_req, req)
in_len = virtio_blk_process_req(handler, in_iov, out_iov, in_num, out_num)
case VIRTIO_BLK_T_IN:
case VIRTIO_BLK_T_OUT
if (is_write)
qemu_iovec_init_external(&qiov, out_iov, out_num);
else read
qemu_iovec_init_external(&qiov, in_iov, in_num)
write -> ret = blk_co_pwritev(blk, offset, qiov.size, &qiov, 0)
blk_co_pwritev_part
blk_co_do_pwritev_part
read -> ret = blk_co_preadv(blk, offset, qiov.size, &qiov, 0)
case VIRTIO_BLK_T_WRITE_ZEROES
in->status = virtio_blk_discard_write_zeroes(handler, out_iov, out_num, type)
vduse_blk_req_complete(req, in_len)
vduse_blk_inflight_dec(vblk_exp)
vduse_blk_inflight_inc(vblk_exp)
qemu_coroutine_enter(co)
eventfd_write(vduse_queue_get_fd(vq), 1)
.disable_queue = vduse_blk_disable_queue,
};
Vduse 是一种高效的方法,可将存储服务导出到本地主机,供容器和其他进程使用。其 umem 注册扩展已合并到 Kernel 6.0 中。virtio-blk 相关代码与 vhost-blk 重复,在进一步重构 virtio-blk-tgt 后应删除重复代码。
SPDK中的VDUSE设备创建命令参考如下:
vduse device can be created/deleted by rpc methods like:
$./build/bin/spdk_tgt &
$./scripts/rpc.py vduse_create_blk_controller vduse0 null0 -n 3 -s 1024
$./scripts/rpc.py vduse_delete_controller vduse0
And attach/detach them with vdpa tool.
VDUSE简介-virtio 的软件定义数据路径: https://www.redhat.com/ja/blog/introducing-vduse-software-defined-datapath-virtio
网络虚拟化——vduse: https://blog.csdn.net/dillanzhou/article/details/121689985
SPDK VDUSE: https://review.spdk.io/gerrit/plugins/gitiles/spdk/spdk/+/3ab3bf7d382e8137fc1b25b4cb69ce7d1d9ffa92
基于SPDK的Ublk和Vduse的用户空间块服务: https://blog.csdn.net/weixin_37097605/article/details/137362344
博客: https://cloud.tencent.com/developer/user/5060293/articles | https://logread.cn | https://blog.csdn.net/ssbandjl | https://www.zhihu.com/people/ssbandjl/posts
https://chattoyou.cn(吐槽/留言)
https://cloud.tencent.com/developer/column/101987
技术会友: 欢迎对DPU/智能网卡/卸载/网络,存储加速/安全隔离等技术感兴趣的朋友加入DPU技术交流群
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。