virtio packed virtqueue

spilt virtqueue因其简约的设计而备受欢迎,但是它有一个基本的问题:avail, used ring 是分离的,cpu cache miss的概率比较大,从硬件角度来看意味着每个descriptor的读写操作都需要几个PC事务。packed virtqueue通过将三个ring合并到一起来改善这个问题.不过这种方式看起来非常复杂,远不如split virtqueue简约。在split版本中如果认识到在获取driver提供的数据之后device可以丢弃或者重写覆写这块buffer区域,基本就能理解packed virtqueue的实现了。

在virtio设备初始化阶段,split virtqueue和packed virtqueue的过程是相同的:协商feature, 申请virtqueue,packed virtqueue的布局如下:

struct virtq_desc { le64 addr;le32 len;le16 id;le16 flags;
};

id域不再是device寻找buffer的索引了,它只是一个对driver有意义的值。
driver同时维护了一个内部的bit位表示ring的回绕情况,初始化为1.每次回绕都会反转该位,我们称为wrapper_count,只能为0和1。
和split 中相同,第一步是更新addr,len, id, flags域。但是packed增加了两个新的flag:AVAIL(0x7) and USED(0x15)。为了标记一个desc为avail,driver必须置位AVAIL(0x7)为它的回绕标志,对于used是相反的。
一个二进制标记很容易实现,但是它会阻碍一些又用的优化,后面会讲。

通过下面的例子讲packed 数据是如何流转的.
driver现在有一个device可写的buffer:起始于0x80000000 ,长度为0x1000。之后更新desc指向这块buffer, flags中AVAIL位和wrapper_count,系统初始化为1,所以目前flags位:W|A, 整个desc如下:

Avail idx Address Length ID Flags Used idx
0x80000000 0x1000 0 W|A

注意,avail idx和used idx列只是为了展示状态,他们并不在descriptor table中,只是device和driver的内部状态,每一端都需要直到需要下一次该从哪里读或者写,同时device还需要追踪driver的wrapper_counter.

最后,driver需要通知device,如step3.
如图中所示,没有avail,used ring,只有一个descriptor table.

和driver一样,device内部也维护一个wrapper_counter,初始化为1,同时它假设driver内的wrapper count是初始状态是1。当device第一次寻找driver发布的buffer时,它会需找ring中的第一满足条件的desc:flag.AVAIL, flag.USED等于driver内部的wrapper counter.
当device消费完数据,通知driver时,和split used ring一样,需要更新写入数据的长度,id为消费的desc id。最后device会设置flag.AVAIL和USED,和它内部维护的wrapper counter相同。
如下图,descriptor table如下。当flag.AVAIL == flag.USED == device.wrapper_counter时,device知道这个buffer已经返回给driver.返回的地址不重要,重要的是id。

Avail idx Address Length ID Flags Used idx
0x80000000 0x1000 0 W|A|U

当driver使用完descriptor table之后,会反转内部的wrapper_counter。所以在第二轮,再次设置avail desc时就完全不一样了,flags.AVAIL = 0并且flags.USED=1 表示avail,同样的device会意识到这种情况。让我们看一个例子:

如果我们descriptor table中有两个desc时,driver内部的wrapper_counter初始化为1,并且填充了两个avail buffer后,driver反转它内部的wrapper_counter,此时为0,此时descriptor table如下表:

Avail idx Address Length ID Flags Used idx
0x80000000 0x1000 0 W|A
0x81000000 0x1000 1 W|A

在此之后,device意识到descritor id 0 和 id 1都已经是可用状态: flags.AVAIL 和 它内部的wrapper_counter相同,并且没有设置 flags.USED. 如果device使用了desc id 1的buffer,descriptor table如下标,注意此时desc id 0仍然属于desc.

Avail idx Address Length ID Flags Used idx
0x80000000 0x1000 1 W|A|U
0x81000000 0x1000 1 W|A

现在,driver 看到desc id 1已经被使用过了,因为 flags.AVAIL == flags.USED == device.wrapper_counter.
如果device 继续使用了desc id 0,descriptor table就如下表:

Avail idx Address Length ID Flags Used idx
0x80000000 0x1000 1 W|A|U
0x81000000 0x1000 0 W|A|U

但是更有趣的是仍然乱序使用,device目前只使用desc id 1, driver 看到desc id 1已经被使用,driver再次使用了 desc id 1作为avail buffer, 如下图,两个id都是 1,而且device都可用

Avail idx Address Length ID Flags Used idx
0x81000000 0x1000 1 W|(!A)|U
0x81000000 0x1000 1 W|A

注意:在上次的wrap之后,driver需要反转它的wrapper_counter和used flag.当device 回绕寻找avail buffer时,它需要寻找这种符合条件的desc,很明显, index 1 的 desc id 1符合条件,之后回绕之后反转wrapper_counter, 此时index 0 和 index 1 的 buffer都可用,此时buffer id 0和 id 1都属于device,它可以再次决定首先使用desc id 1.

Chained descriptors

链式的descriptor工作方式:在desc中没有 next 域来指向下一个 desc 的索引,因为 下一个 desc一定会在它的下一个位置。但是在split used ring中, 只需要返回chain desc的头部,根据next 就可以找到后面所有的 desc; 在packed virtqueue中需要返回 尾部的desc.

回到used ring,每次我们使用chained descriptors,used idx都会滞后于avail idx. 在一次性传输多个avail desc给device,我们只会在avail ring中放一个头部的 idx, device 使用完之后也只会返回一个头部的idx . 在split ring中,这个不是问题,但是在packed virtqueue中这个就会引入一个问题。

最直接的方法是,device 标记 chained desc中的每一个desc为 USED。 但是这种方式代价比较大,因为我们修改的是共享内存,会引入cache颠簸性问题。
但是, driver 已经知道这个chain, 它可以忽略最后一个id之前的所有的chain desc,这也就是我们为什么需要比较used/avail 和driver/device wrapper_counter: 在跳转后,我们不知道下一个 desc是否是可用的,或者
如下表, 我们一个desc table中有4个desc, driver通过chain desc发布了三个avail desc:

Avail idx Address Length ID Flags Used idx
0x80000000 0x1000 0 W|A
0x81000000 0x1000 1 W|A
0x82000000 0x1000 2 W|A
0

在此之后, device 发现index 0之后的desc是可用的,然后将其标记为used,然后发布给driver: 只在 index 0位置处写入了flags.USED, 并且 id = 2; 当driver 查看是否已经被使用,它将会跳过index 1 和 2, driver知道这3个desc是chained 关系。

Avail idx Address Length ID Flags Used idx
0x80000000 0x1000 2 W|A|U
0x81000000 0x1000 1 W|A
0x82000000 0x1000 2 W|A
0

现在 driver 发布了另外带有2 个desc的chained desc, 在这个过程中它会反转内部的wrapper,状态如下表:

Avail idx Address Length ID Flags Used idx
0x81000000 0x1000 1 W|(!A)|U
0x81000000 0x1000 1 W|A
0x82000000 0x1000 2 W|A
0x80000000 0x1000 0 W|A

之后,device 使用这2个desc的chained desc, 现在只需要更新chained desc中的第一个: id = 0, flags.AVAIL = flags.USED = device.wrapper_counter;

Avail idx Address Length ID Flags Used idx
0x81000000 0x1000 1 W|(!A)|U
0x81000000 0x1000 1 W|A
0x82000000 0x1000 2 W|A
0x80000000 0x1000 0 W|A|U

尽管index 0位置处的desc看起来像是avail的:flags.AVAIL != flags.USED, 但是device通过判断它的flags组合知道它不是,正确的标志应该是flags.AVAIL = 0, flags.USED = 1;

Indirect descriptors

indirect descriptor工作机制和split virtqueue非常相似。首先driver 分配一个 indirect desc 的空间,它和普通的packed virtqueue desc中的布局是完全相同的。 之后设置每一个indirect desc指向的buffer信息,如下图中的step 1-2; 最终在desc 中设置,指向indirect desc table,并且标记上 VIRTQ_DESC_F_INDIRECT ,如下图step 3,起始地址和长度对应于indirect desc table的信息。

在packed virtqueue中, buffer的顺序完全依赖于indirect desc table中的顺序,在indirect desc table中 id 是完全没有意义的,唯一有意义的flag是VIRTQ_DESC_F_WRITE。 driver 通知 device的条件和普通的packed virtqueue是相同的。

假如我们要发布三个buffer,通过indirect table,首先需要申请三个desc空间的indirect desc table,长度为48字节,更新indirect table项指向三个buffer:

Avail idx Address Length ID Flags Used idx
0x80000000 0x1000 W
0x81000000 0x1000 W
0x82000000 0x1000 W

假如indirect table的地址在 0x83000000 处,我们需要更新desc table, 第一个可用的位置为index 0:

Avail idx Address Length ID Flags Used idx
0x83000000 48 0 A|I

当indirect buffer被消费后,device 将会在index 0 返回 id 0, flags.USED. desc表和普通的packed desc表基本相似,只是flags.INDIRECT被置位了。

Avail idx Address Length ID Flags Used idx
0x80000000 48 0 A|U|I

在此之后,device就不能再访问这个indirect table除非driver将其再次发布为avail状态,此时driver可以释放这块内存或者重用它。

Notifications

和split virtqueue一样, device/driver 每一端都维护了一个相同的结构来控制对方发送通知给自己。driver维护的对于device来说是只读的,同样的,device维护的对于driver来说也是只读的。

struct pvirtq_event_suppress { le16 desc;le16 flags;
};

flags的值的意义:
0: 通知总是enabled的
1:通知总是disable的
2: 当desc满足条件时再通知
如果flags == 2, 另一方判断是否通知的条件:

  1. 内部的wrapper counter == desc的最高位
  2. desc的index == desc & 0x7fff
    这种方式通过VIRTIO_F_RING_EVENT_IDX来呈现,需要device/driver都支持并且在初始化过程中协商好;
    这些机制都不时100%可靠的,因为当我们设置这些值时可能对方已经发送了notifications, 如果真的不想被通知,那就直接禁止它吧。
    注意,packed virtqueue的size并不一定要求是2的次方,所以notifications结构可以和desc一同放在同样的一个页内,这样在某些实现上有优势。

virtio系列-packed virtqueue相关推荐

  1. virtio split ctrl virtqueue

    1.Split virtqueue 组成结构: • Descriptor Table • Available Ring • Used Ring 数据结构: Descriptor Table Avail ...

  2. virtio vring原理

    文章目录 vring原理 Virtqueues Descriptor Table Available Ring Used Ring vring数据结构 vring_virtqueue vring_vi ...

  3. virtio技术(3)virtqueue机制

    概述 virtio的关键技术是virtqueue机制,其提供了一套统一的用于virito前端和后端的通信机制.对于每个virtqueue包含三个部分: Descriptor Area:用于描述数据bu ...

  4. DPDK vhost-user之packed ring(六)

    virtio1.1已经在新的kernel和dpdk pmd中陆续支持,但是网上关于这一块的介绍却比较少,唯一描述多一点的就是这个ppt:https://www.dpdk.org/wp-content/ ...

  5. Linux虚拟化KVM-Qemu分析(九)之virtio设备

    目录 1. 概述 2. 流程分析 3. tap创建 - 网卡后端设备 4. virtio-net创建 4.1 数据结构 4.2 流程分析 4.2.1 class_init 4.2.2 instance ...

  6. Virtio: An I/O virtualization framework for Linux | 原文

    <Virtio: An I/O virtualization framework for Linux> <KVM Virtio: An I/O virtualization fram ...

  7. Virtio原理简介

    目录 Virtio规范简介 相关数据结构 Vring机制简介 相关文章 实现IO虚拟化主要有三种方式:全虚拟化.半虚拟化和透传.全虚拟化Guest OS不会感知到自己是虚拟机,也无需修改Guest O ...

  8. Linux虚拟化:Virtio: 一个 I/O 虚拟化框架

    <Virtio: An I/O virtualization framework for Linux> 目录 什么是 virtio# 为什么是 virtio# virtio 的架构# vi ...

  9. VIRTIO PCI 设备

    Virtio的代码主要分两个部分:QEMU和内核驱动程序.Virtio设备的模拟就是通过QEMU完成的,QEMU代码在虚拟机启动之前,创建虚拟设备.虚拟机启动后检测到设备,调用内核的virtio设备驱 ...

最新文章

  1. 电动汽车换电的优缺点分析
  2. 自制Ghost XP SP3 启动光盘(一)
  3. 使用ganymed-ssh2-build通过ssh获得远程服务器参数
  4. java 设计原则_【无尽的编程之路】(java)-设计模式六大原则
  5. 框架开发中的junit单元测试
  6. grub安装的 三种安装方式
  7. memset 还可以这样用
  8. 区块链与边缘计算(3)系统介绍
  9. 大数据可视化html模板开源_让数据栩栩如生,蚂蚁金服新一代开源数据可视化解决方案——AntV...
  10. Web pack misc
  11. SPOJ687 Repeats(重复次数最多的连续子串)
  12. 万兆网卡实际吞吐量_AKITIO 10G/NBASE-T PCIe 网卡开箱拆解评测
  13. 中科院信工所经验_2021中科院信息工程研究所电子信息专业考研经验指导分享...
  14. 互联网晚报 | 2月18日 星期五 | 高途宣布停止高中学科辅导服务;小红书启动最严医美专项治理;FF 91量产版2月23日发布...
  15. springboot整合mysql5.7_详解SpringBoot整合MyBatis详细教程
  16. 记一次.net mvc中 RouteAttribute 不起作用
  17. 洛谷——P1838 三子棋I
  18. gc android,Android GC Log解读
  19. pytorch--- .zero_grad()
  20. JavaFX及Java客户端技术的未来

热门文章

  1. ​【创作赢红包】sql常用语法
  2. Redis 集群清空所有节点数据
  3. unrecognized JEDEC id 海思3516 spi nor flash 型号 问题 ./drivers/mtd/spi-nor/spi-nor.c
  4. html更改纵坐标数值,教大家Excel中图表坐标轴的数值怎么修改
  5. GAEA沙丘地形创建
  6. 华为路由交换机端口的三种模式
  7. 【Java】超级简单的随机点名器
  8. 在Oracle和MySQL上安装hr schema、example和Scott schema
  9. msp430G2553串口通信_launchpad串口通信
  10. JavaScript解除事件绑定处理程序 js事件绑定解除