virtio vring原理

news2024/10/7 18:24:07

vring原理


virtio 设备上进行批量数据传输的机制被称为 virtqueue 。每个设备可以拥有零个或多个 virtqueue ,当 Driver 想要向设备发送数据时,它会填充 Descriptor Table 中的一项(或将几项链接在一起),并将描述符索引写入 Available Ring 中,然后它通知 Device ,当 Device 完成后,它将描述符索引写入 Used Ring 中并发送中断。

VirtioGuest 中实现了前端驱动,在 Hostqemu)中实现了后端驱动,前后端之间通过 Virtqueue (虚拟队列)交换数据, Host 中会使用后端驱动程序模拟一个 PCI 设备,因此也称前端驱动为 Driver ,后端驱动为 DeviceGuestHost OS 上表示为一个 Qemu 的进程, Guest OSPA 实际上也属于 Host OS 的地址空间,因此 Virtio 采用的 Virtqueue 的方式来避免了 GuestHost 主机间数据的复制。

virtio 规范中定义了每个 virtio 设备包含的结构(https://docs.oasis-open.org/virtio/virtio/v1.1/virtio-v1.1.pdf第2节 Basic Facilities of a Virtio Device):

* Device status field;设备状态字段
* Feature bits;特征位
* Notifications;通知
* Device Configuration space;设备配置空间
* One or more virtqueues;一个或多个virtqueues

Virtqueues

virtqueue 用作在 GuestHost 之间传递数据, Host 可以在用户态( qemu )实现,也可以在内核态( vhost )实现。在 virtio 规范中定义了每个 Virtqueues 包含的结构(第2.6节 Virtqueues):

* Descriptor Table;描述符
* Available Ring;driver提供给device的额外数据
* Used Ring;device提供device的额外数据

Descriptor Table

描述符表,每一项描述符指向一片内存,内存类型可以分为 out 类型和 in 类型,分别代表输出和输入,而内存的管理都由 driver来负责。其中 Descriptor Table 指的是驱动和设备的缓冲区,由 Queue SizeDescriptor 组成。 Descriptor 中存有 GPA 的字段 addr ,长度字段 len ,可以链接 next Descriptornext 指针等(形成描述符链)。如果协商了 VIRTIO_F_INDIRECT_DESC feature 则可以使用 Indirect Descriptors 来增加 ring 的容量,详见 desc。

Available Ring

可用描述符区域,用于记录设备可用的描述符 ID ,它的主体是数组 ring ,实际就是一个环形缓冲区;Guest 通过 Avail RingHost 提供 buffer ,指示 Guest 增加的 buffer 位置和当前工作的位置,

Available Ring 中的每个条目是一个是描述符链的头部,它仅由 Driver 写入并由 Device 读取, Device 获取 Descriptor 后, Descriptor 对应的缓冲区可能是可读的也可能是可写的,可读的用于 Driver 发送数据,可写的用于接收数据,详见 avail。

Used Ring

已用描述符区域,用于记录设备已经处理完的描述符 ID ,同样,它的 ring 数组也是环形缓冲区,与avail_vring不同的是,它还记录了设备写回的数据长度;Host 通过 Used RingHost 提供信息,指示 Host 处理 buffer 的位置。device 通过 used ring 来将消费过的 buffer 返回给 driver,详见 used。


vring数据结构

在这里插入图片描述

图片引用自virtio数据结构总览 | 图_rtoax的博客-CSDN博客_virtio数据结构详解

在这里插入图片描述

图片引用自:VirtIO实现原理——vring数据结构_燕无鸻的博客-CSDN博客_vring

在这里插入图片描述

图片引用自:qemu-virtio基本原理 - CodeAntenna

在这里插入图片描述

图片引自:Introduction to VirtIO (oracle.com)

Linux内核VRing数据结构

在这里插入图片描述

图片引自:Introduction to VirtIO (oracle.com)

QEMU中VRing数据结构

vring_virtqueue 是一个 virtqueue,它将 VRing 的实现隐藏在 virtqueue 下面,当一个 virtio-blk 设备真正要发送数据时,只要传入 virtqueue 就能找到 VRing 并实现数据收发

vring_virtqueue

源码位置 /drivers/virtio/virtio_ring.c

struct vring_virtqueue {
    /* 设备看到的VRing */
	struct virtqueue vq;

	/* Is this a packed ring? */
	bool packed_ring;

	/* Is DMA API used? */
	bool use_dma_api;

	/* Can we use weak barriers? */
	bool weak_barriers;

	/* Other side has made a mess, don't try any more. */
	bool broken;

	/* Host supports indirect buffers */
	bool indirect;

	/* Host publishes avail event idx */
	bool event;

	/* Head of free buffer list.
     * 当前Descriptor Table中空闲buffer的起始位置
     */
	unsigned int free_head;
	/* Number we've added since last sync. */
    /* 
     * 上一次通知Host后,Guest往VRing上添加了多少次buffer
     * 每添加一次buffer,num_added加1,每kick一次Host清空
     */
	unsigned int num_added;

	/* Last used index  we've seen.
	 * for split ring, it just contains last used index
	 * for packed ring:
	 * bits up to VRING_PACKED_EVENT_F_WRAP_CTR include the last used index.
	 * bits from VRING_PACKED_EVENT_F_WRAP_CTR include the used wrap counter.
	 */
	u16 last_used_idx;

	/* Hint for event idx: already triggered no need to disable. */
	bool event_triggered;

	union {
		/* Available for split ring */
		struct vring_virtqueue_split split;

		/* Available for packed ring */
		struct vring_virtqueue_packed packed;
	};

	/* How to notify other side. FIXME: commonalize hcalls! */
	bool (*notify)(struct virtqueue *vq);

	/* DMA, allocation, and size information */
	bool we_own_ring;

#ifdef DEBUG
	/* They're supposed to lock for us. */
	unsigned int in_use;

	/* Figure out if their kicks are too delayed. */
	bool last_add_time_valid;
	ktime_t last_add_time;
#endif
};

vring_virtqueue_split

struct vring_virtqueue_split {
	/* Actual memory layout for this queue.
     * 实现数据传输的VRing结构
     */
	struct vring vring;

	/* Last written value to avail->flags */
	u16 avail_flags_shadow;

	/*
	 * Last written value to avail->idx in
	 * guest byte order.
	 */
    /* Guest每添加一次buffer,avail_idx_shadow加1
     * 每删除一次buffer,avail_idx_shadow减1
     */
	u16 avail_idx_shadow;

	/* Per-descriptor state. */
	struct vring_desc_state_split *desc_state;
	struct vring_desc_extra *desc_extra;

	/* DMA address and size information */
	dma_addr_t queue_dma_addr;
	size_t queue_size_in_bytes;

	/*
	 * The parameters for creating vrings are reserved for creating new
	 * vring.
	 */
	u32 vring_align;
	bool may_reduce_num;
};

vring

Virtqueue 有三个核心的数据结构,由 struct vring 负责组织。VRingsguest 与其 host 之间交换数据的一种方式。

struct vring {
	unsigned int num;

	vring_desc_t *desc;

	vring_avail_t *avail;

	vring_used_t *used;
};
desc

desc 在前端驱动里结构是 vring_desc,他是一个环形的 buffer,又被称为 descriptor ring,,其中包括一个数组,数组中每一项的元素包括指向 guest buffer 的地址和长度。另外每一个 desc 还包含一些其他信息。例如这个 desc 指向的不是真实的 buffer 而是一组 desc 时需要标为 INDIRECT 。如果这组 buffer 标记位 device 只写,设置 WRITE ,反之如果只读则清除 WRITE

注意:只有driver 才可以向 desc ring 添加(写入)描述符,后端设备只有在描述符标志表明缓冲区是可写的情况下才能写入设备可写缓冲区。缓冲区可以是只写, 也可以是只读. 但不能同时是可读可写.

flags 用于通知设备或驱动程序一些信息:

  • 下一个描述符是否有相关数据;
  • 此缓冲区是否只写;
  • 缓冲区是否包含简介描述符表;

源码位置 /include/uapi/linux/virtio_ring.h

struct vring_desc {
	__virtio64 addr;	/* guest物理地址 GPA */
	__virtio32 len;		/* buffer长度 */
	__virtio16 flags;	/* 标志位, 比如NEXT, WRITE, INDIRECT */
	__virtio16 next;	/* 下一个链接的描述符索引 */
};

在这里插入图片描述

Descriptor Ring/Table

在上图中存在一个 descriptor ring,包含四个描述符,其中两个连接在一起,其中:

  • descriptor[0] 表明数据缓冲区的 GPA0x600,数据长度为 0x100,标志是 device writable, 没有 next 标志,描述符链无下一个。
  • descriptor[1] 表明数据缓冲区的 GPA0x810,数据长度为 0x200,标志是 device writable-next,即 device 可写,由于存在 next 则此描述符是描述符链的 head ,指向描述符表的 ring[2]
  • descriptor[2] 表明数据缓冲区的 GPA0xA10,数据长度为 0x200,标志是 device writable, 没有 next 标志,描述符链无下一个。
  • descriptor[3] 表明数据缓冲区的 GPA0x525,数据长度为 0x050,标志是 device read-only, 没有 next 标志,描述符链无下一个。
avail

avail 在前端驱动里结构是 vring_avail,也是一个环形 buffer,又被称为 avail vringdriver 需要给 device 提供数据,而数据的元数据存放在 descriptor ring 中,所以此时填入其中的是 descriptor ring 中的 indexdevice 根据 indexdesc ring 中找到对应的 desc ,然后获得地址信息 GPAlen ,最后再转换成 HVA 来消费这些数据。driver 在放置这些 buffer时并不意味着 device需要立即取出处理。

vring_availflags idx 比较重要:

  • flags 的最低位表明 driver 是否需要中断通知 VIRTQ_AVAIL_F_NO_INTERRUPT

  • idx 指向下一个 driver 可用的 desc ring indexidx 存放的 ring[] 数组索引, ring[idx] 存放才是下一次添加的 buffer 头在 Descriptor Table 的位置;

在这 idxflags之后,是和 desc ring 相同长度的数组,即实际可用的 ring 数组,其中存放的是 descriptor ring 中的 index

源码位置 /include/uapi/linux/virtio_ring.h

struct vring_avail {
	__virtio16 flags;	/* 配置标志位 */
	__virtio16 idx;		/* 下一个可用 ring 索引 */
	__virtio16 ring[];	/* 实际的 avail ring */
};

注意:只有 driver 可以配置并向 avail ring 添加条目,而相应的 device 只能从中读取。

avail vring 初始结构如下图:
在这里插入图片描述
在上图中,是一个没有条目和标志设置的可用 vringindex 是表明下一个可用 avail ring 数组。此处avail ring 的索引为idx[0]ring[0]

avail vring 添加第一个条目如下:

在这里插入图片描述
在上述 avail ring 中由于没有 next ,所以 device 只能读取 desc ring 中第一个 ring。此处索引为 avail_ring[0]idx[1]desc_ring[0]

avail ring 添加下一个描述符条目:

在这里插入图片描述
此处的描述符第二个和第三个连接在一起,avail ring[1] 指向描述符链的 headhead 链接到下一个描述符。此索引头为 avail_ring[1]idx[2]desc_ring[1]

添加第三个条目:
在这里插入图片描述
注意这里的描述符索引为3,此处索引为 avail_ring[2]idx[3]desc_ring[3]

上述描述符填充的步骤可总结如下:

  1. driver 分配内存,并添加一个 buffervirtqueue 中;
  2. 更新并填充 desc 指向这块 buffer
  3. 填充完成后,driver 需要发布 descavail ring 并更新描述符索引值。
  4. 发布完成过后,buffer 归属 device 管理, driver 需要通知 device ,如果需要将发送 notifications

在这里插入图片描述

图片引用自 Virtqueues and virtio ring: How the data travels (redhat.com)

used

used 在前端驱动里结构是 vring_useddevice 通过 used ring 将消费过的 buffer 返回给 driver

源码位置 /include/uapi/linux/virtio_ring.h

/* u32 is used here for ids for padding reasons. */
struct vring_used_elem {
	/* Index of start of used descriptor chain. */
	__virtio32 id;	/* 索引指向desc ring上的元素 */
	/* Total length of the descriptor chain which was used (written to) */
	__virtio32 len;	/* 写入descriptor buffer的数据长度 */
};

typedef struct vring_used_elem __attribute__((aligned(VRING_USED_ALIGN_SIZE)))
	vring_used_elem_t;

struct vring_used {
	__virtio16 flags;	/* 配置标志位 */
	__virtio16 idx;		/* 下一个可用的 avail ring 索引 */
	vring_used_elem_t ring[];	/* 实际使用的ring数组 */
};

used ring 结构与avail ring 结构基本一致:
在这里插入图片描述
avail ring 类似, used ring 也使用 index 字段, index 字段与 avail ringindex 字段相同,不同之处在于对于 used ring ,它表示 used ring 数组中的下一个可用条目。

注意:与 avail ring 相反,只有 device 以配置和添加条目到 used ring ,而相应的 driver 程序只能从它读取。

首先处理第一个 used ring 条目,第一个描述符的数据缓冲区被标记为设备可写,假设设备将字节写入描述符的设备可写缓冲区,写入的字节数为0x50,写入后的结果如下图所示:

在这里插入图片描述
在上图中,能看到使用过的 ring 条目,idx 为1表示下一个可用的条目索引为1。

处理第二个 used ring 条目,第二个描述符的数据缓冲区被标记为设备可写,且存在两个节点,假设设备将字节写入描述符的设备可写缓冲区,写入的字节数为0x350,写入后的结果如下图所示:
在这里插入图片描述
设备写入的字节为0x350,长度超出了 desc[1] 的可写长度,但由于used ring 写入的描述符数据长度表示每个链式描述符数据缓冲区的字节总数, 所以超过的部分将被写入至链接的下一个数据缓冲区。此时,idx 指向下一个可用的条目索引2。

最后,处理第三个条目,此处由于 flags 为只读,所以此处写入后的结果如下:
在这里插入图片描述
device 收到通知后,将会通知 driver 程序,并使用 used queueflags 通知。

在这里插入图片描述

图片引用自 Virtqueues and virtio ring: How the data travels (redhat.com)


vring使用实例

例如: Virtio-net 设备发包过程为例讲解上述机制, Driversk_buffer 填充进 scatterlist table 中(只是设置地址没有数据搬移),然后通过计算得到 GPA 并将 GPA 写入 Descriptor Table 中,同时将 Desc chainhead 记录到 Available Ring 中,然后通过 PIO 的方式通知 DeviceDevice 发包并更新 Used Ring

在这里插入图片描述

图片引用自Virtio原理简介 | Lauren·weblog (lihanlu.cn)


参考

VirtIO实现原理——vring数据结构_燕无鸻的博客-CSDN博客_vring

virtio数据结构总览 | 图_rtoax的博客-CSDN博客_virtio数据结构详解

VirtIO实现原理——vring数据结构_燕无鸻的博客-CSDN博客_vring

https://docs.oasis-open.org/virtio/virtio/v1.1/virtio-v1.1.pdf virtio space 1.1

【原创】Linux虚拟化KVM-Qemu分析(十一)之virtqueue - LoyenWang - 博客园 (cnblogs.com)

Virtqueues and virtio ring: How the data travels (redhat.com)

Introduction to VirtIO (oracle.com)

virtio系列-split virtqueue数据流_wjx5210的博客-CSDN博客_virtio split ring

Virtio原理简介 | Lauren·weblog (lihanlu.cn)

virtio-net 实现机制 - 豆奶特 (dounaite.com)

qemu-virtio基本原理 - CodeAntenna

😲

📸

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

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

相关文章

圣诞树拼图游戏unity制作

2022年圣诞节到来啦,很高兴这次我们又能一起度过~ 一、前言 提示:使用unity来制作一个拼图游戏,图片便是圣诞树。 二、创意名 圣诞树拼图游戏 三、效果展示 圣诞树拼图游戏最终效果。 游戏中效果如图: 游戏拼图完成后效果如图&am…

vue实现随机生成分享海报(内容动态)

大家好,我是雄雄。 前言 昨天写了篇文章:自己整理的vue实现生成分享海报(含二维码),看着网上的没实现 主要是介绍了如何使用vue实现,动态分享内容为海报,且附带二维码,扫描二维码能…

shell脚本四剑客之awk详解

文章目录awk的介绍awk能够干什么awk的格式工作原理:记录和域内建变量的用法1. FS2. OFS3.RS4. ORS5. NF6. NRBEGIN 和END语句块常见案例1. 使用NR行号提取ip2. 打印UID小于10的账号名称和UID信息3. 数学运算4. AWK打印硬盘设备名称,默认以空格为分割&…

UDP用户数据报协议(计算机网络-运输层)

目录 UDP 概述 UDP 的主要特点 UDP 的问题 UDP的多路分用模型 UDP 的首部格式 UDP 概述 用户数据报协议(User Datagram Protocol,UDP) UDP 只在 IP 的数据报服务之上增加了很少一点的功能,即端口的功能和差错检测的功能 虽…

计算机网络——网络层功能概述

网络层 网络层的主要任务是把分组从源端传到目的端,为分组交换网上的不同主机提供通信服务。网络层的传输单位成为数据报。 数据报是一组比较长的数据,分组则是将数据报划分为不同的片段 网络层的第一个功能:路由的选择和分组的转发。 网络层…

python词云图词频统计

目录 一:安装必要的库 二:数据分析 条形图可视化 三:数据分析 词频统计 词云图可视化 一:安装必要的库 导入必要的库 import collections # 词频统计库 import os import re # 正则表达式库 import urllib.error # 指定url&…

WRF进阶:antro_emiss工具处理全球大气人为排放(EDGRA_HTTPs)/人为排放清单前处理

本内容视频版讲解:全球人为排放处理 介绍 一般人为数据的排放前处理使用pre_chen_src工具,然而pre_chen_src处理后的文件并不是WRF所能读取的文件格式,需要使用onvert_emiss.exe,生成WRF需要的人为排放的nc数据。 在WRF-chem3.6…

煤矿视频监控分析检测 yolo

煤矿视频监控分析检测利用python基于yolo深度学习架构,对现场画面进行实时分析检测。我们使用YOLO(你只看一次)算法进行对象检测。YOLO是一个聪明的卷积神经网络(CNN),用于实时进行目标检测。该算法将单个神经网络应用于完整的图像,然后将图像…

单片机——LED点阵

1. 基本介绍 LED点阵 LED点阵是由发光二极管排列组成的显示器件,通常应用较多的是88点阵,然后通过多个88点阵组成不同分辨率的LED点阵显示屏,如4个88组成的1616点阵 8*8点阵由64个LED组成,每个LED是放置在行线和列线的交叉点上…

LVGL学习笔记3 - 样式Style

目录 1. 初始化样式 2. 设置样式 3. 添加和移除样式 4. 验证 5. 状态(State) 6. 部分(Parts) 样式用于设置对象的外观,比如颜色等属性,存储在 lv_style_t 变量中,这个变量应该是static…

不写一行代码(二):实现安卓基于PWM的LED设备驱动

文章目录一、前言二、系列文章三、准备工作3.1 查找PWM引脚3.2 原理图:确认引脚位置3.3 PWM Controller四、查阅PWM bindings五、编写设备树节点5.1 实现节点:pwm-leds5.2 测试命令六、后语一、前言 在完成了基于GPIO的LED设备驱动的文章后,…

3天学会撰写软件发明专利——3.生命周期

“无意中发现了一个巨牛的人工智能教程,忍不住分享一下给大家。教程不仅是零基础,通俗易懂,而且非常风趣幽默,像看小说一样!觉得太牛了,所以分享给大家。点这里可以跳转到教程。”。 一、专利授权生命周期…

4.1 协程:协程基础

1.协程 协程,又称微线程。协程是python个中另外一种实现多任务的方式,只不过比线程更小占用更小执行单元(理解为需要的资源)。 为啥说它是一个执行单元,因为它自带CPU上下文。这样只要在合适的时机, 我们可…

C++类和对象概念及实现详解(上篇)

文章目录 一、什么是类和对象呢? 1、类的引入 2、类的定义 3、类的访问限定符 4、类对象的储存方式 5、this指针的特性 二、类的六个默认成员函数详解 1、构造函数 2、析构函数 3、未完待续…… 标题:类和对象概念及实现详解(上篇&#xff0…

vue3 antd table表格——自定义单元格样式(二)利用rowClassName给table添加行样式

vue3 antd项目实战——修改ant design vue组件中table表格的默认样式(二)知识调用场景复现修改table表格的行样式一、rowClassName添加行样式二、表格的不可控操作写在最后知识调用 文章中可能会用到的知识链接vue3ant design vuets实战【ant-design-vu…

从头开始用树莓派做一个NAS【最新超详细教程】

一、概述 众所周知在办公的时候两台电脑之间经常倒数据资料非常麻烦,而NAS可以很好的解决这个问题。树莓派搭建NAS方法有很多,我们之前也拍过直接用Samba、FTP这些来实现NAS功能,但是这些需要你会在命令行进行配置,而且对于新手用…

【Linux】Linux权限管理

目录一.Linux用户权限1.权限的概念2.用户分类3.切换用户4.sudo提权二.Linux文件权限1.文件属性2.文件类型3.文件角色划分4.基本权限三.文件访问权限的相关设置方法1.chmod2.chown3.charp4.file5.权限拒绝四.默认权限umask五.目录的权限六.粘滞位1.背景2.准备3.情况4.粘滞位一.L…

初识Docker:(1)什么是docker

初识Docker:(1)什么是docker项目部署的问题Docker总结项目部署的问题 大型项目组件较多,运行环境也较为复杂,部署时会碰到一些问题: 依赖关系复杂,容易出现兼容性问题开发、测试、生产环境有差…

git revert以及revert的恢复

一:背景与方案 在工作中遇见的这样的场景: 场景一: 已经merge到待发布的版本分支中的功能需要移除当前的分支,改在后续版本发布,示意图如下,展示的是commit序列, 这里想要移除的功能是commi…

[python库] base64库的基本使用

1. base64是什么 base64是一种二进制到文本格式的编码方式。具体来说就是将byte数组编码为字符串的方法,而编码出来的字符串只包含ASCII基础字符。 虽然说base64是一种编码方式,但是它并不推荐作为常规的加密算法使用,因为该算法的加解密算法…