栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > .Net

关于 virtio 的重要知识点总结

.Net 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

关于 virtio 的重要知识点总结

virtio 相关介绍文章很多,这里总结一些自认为一些关键的技术点,

1,virtio ring(vring)队列由 guest 准备(申请内存),vring(split)包含三部分:desc[], avail, used,这三部分被放置在申请的同一块内存区域。初始化时,需要将desc[0 ~ vring.num-1]组织成一个list,

vq->free_head = 0;
for (i = 0; i < vring.num-1; i++)
        vq->split.vring.desc[i].next = cpu_to_virtio16(vdev, i + 1);

vring.avail 所指向的 desc 也由 guest 准备,guest 给队列准备 desc(实际上是准备desc[i~j].addr, .len)  chain分两种:
     1),发送时准备,如 virtio-blk 所用队列以及 virtio-net 中 guest 主动发送数据用到的队列;
     2),事先准备好,如 virtio-net 中 host 主动发送数据的队列 (virtqueue_add_inbuf);

desc chain 包含的 desc 的个数由具体 virtio 设备前端驱动下发的 sg 个数决定(out_sg, in_sg);

组成 desc chain 的 desc[i~j].flags 都设置了 VRING_DESC_F_NEXT 标记,表明 i~j 索引的desc组成已了一个chain(根据NEXT标记 host 就知道了需要获取的 desc chain的所有desc)。当前 desc chain 的起始位置 i 由 vq->free_head 决定,之后将 j 赋值给 vq->free_head 用于下次准备时 desc chain 的起始位置;

当guest回收 desc chain 时(detach_buf_split),先从 used->ring[vq->last_used_idx].id 中获取 desc chain 的头 desc 在 desc[] 中的 index x,然后根据 desc[x].flags & VRING_DESC_F_NEXT与 desc[x].next 循环下去就能获取到 chain 的所有desc。假设desc chain 的最后一个 desc 的 index 是y,那么最后设置 desc[y].next = vq->free_head,vq->free_head = x。这样把刚释放的desc chain 与原有的 free 的 chain 就连在一起了,x 成为新的 free_head。

支持 indirect 的 desc,即 vring.desc[].addr 不再表示一个 sg 的地址,而是指向一下新申请的 desc table, 这个 table 中才真正存储了 sg 的 addr,

        if (indirect) {
                
                dma_addr_t addr = vring_map_single(
                        vq, desc, total_sg * sizeof(struct vring_desc),
                        DMA_TO_DEVICE);
                if (vring_mapping_error(vq, addr))
                        goto unmap_release;

                vq->split.vring.desc[head].flags = cpu_to_virtio16(_vq->vdev,
                                VRING_DESC_F_INDIRECT);
                vq->split.vring.desc[head].addr = cpu_to_virtio64(_vq->vdev,
                                addr); // addr 是 desc table 数组的 dma 地址
                                       // 将 addr 存入 desc[head].addr, 这样 desc[head].addr 就不再表示一个 sg 的地址,而是指向一个 desc table
                                   

                vq->split.vring.desc[head].len = cpu_to_virtio32(_vq->vdev,
                                total_sg * sizeof(struct vring_desc));
        }

2,virtio-blk 只需要一个 virtio ring 队列,且是单方向的队列,读写的发起方都是guest,guest需要发送(or 称为读写)数据时向队列中填充desc;

3,virtio-net,virtio-vsock,virtio-console等需要若干个virtio ring 队列pair,pair 中的一个是guest主动发送数据时使用的队列,另一个是用于host主动发送数据时使用的队列;
需要 guest事先填充好 host 所用的数据发送队列,以便host需要发送数据时可以获取到desc;
且需要guest收到host发送数据后,回收desc并回填到host所用的队列,方便host继续使用;

4,vhost-blk 与 其他 vhost-net,vhost-vsock等不同,在 host 侧需要获取 guest 读写 buffer 对应的page,进而组织为bio:

GVAs
   -> GPAs(guest fill to vring.desc chain) 
       -> host kernel get in&out io vectors(iovs) from desc chain 
	      -> host kernel call translate_desc() to convert GPA(iov->iov_base) 
             to HVA for each iov
                -> kernel call get_user_pages(HVA) for each iovs 
                   -> make bio and submit

而对于其他的 vhost 驱动则是,直接读取数据:
               GVA -> GPA ->  HVA(guest fill it to vring.desc) -> copry_from_iter;

5,用户态 virtio-blk 后端驱动(qemu/kvmtool) 使用 direct io 模式,

GVAs
 -> GPAs (guest fill to vring.desc chain) 
    -> host get in&out io vectors(iovs) from desc chain 
      -> host call guest_flat_to_host() to convert GPA(iov->iov_base) to HVA for 
         each iov
         -> host call io_submit(direct aio) to sumbit iovs((already HVA))
           -> kernel call get_user_pages() for iovs 
             -> make bio and submit;

用户态 virtio-blk 后端驱动(qemu/kvmtool) 使用 buffer 模式,

GVAs
 -> GPAs(guest fill to vring.desc chain) 
    -> host get in&out io vectors(iovs) from desc chain
      -> host call guest_flat_to_host() to convert GPA(iov->iov_base) to HVA for 
         each iov
          -> host call io_submit(buffer aio) to sumbit iovs(already HVA)
           -> 1, if data for iovs already cached(found page from page cache 
                 filp->f_mapping based on page index of iovs' pos in file or 
                 blkdev), call copy_page_to_iter() to copy page to iovs, then done;
              2, if data for iovs not cached, alloc page and read data from disk to 
                 this page, then call copy_page_to_iter() to copy page to iovs and 

6,spilt vring 有一个基本的问题: avail ring, used ring 是分离的,分离会造成多次访存,cpu cache miss的概率比较大,尤其在硬件场景下(virtio offload硬件),由此造成比较大的硬件pci操作开销。具体为 split 的 avail ring.{idx,flags} 与 used ring.{idx,flags} 是分开的(即二者基本不在同一个cacheline中),而前端或后端的代码都需要访问到 avail ring.{idx,flags} 与 used ring.{idx,flags}。例如后端需要从rx virng中取出 avail 的 desc 来填充数据,需要先读取 rx vring 的 avail->idx(vhost_get_vq_desc->vhost_get_avail_idx) 然后与本地存储的 vq->last_avail_idx desc 进行比较来进一步判断有无可用的 desc 以供本次给 guest 发送数据;填充完 desc 后,需要更新 rx vring 的 used->idx(vhost_add_used_n->vhost_put_used_idx->vhost_put_user)然后 signal guest。可见在后端发送数据的流程中,avail->idx 与 used->idx 会同时被访问到。

为解决上述问题,virtio spec1.1 引入了packed vring,packed vring通过将三个ring合并到一起来改善这个问题。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/984114.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号