Skip to the content.

virtio split ring 结构

它分成 3 块: +———————-+ | Descriptor Table | +———————-+ | desc[0] -> buf A | | desc[1] -> buf B | | desc[2] -> buf C | | … | +———————-+ +———————-+ | Avail Ring | guest 写 +———————-+ | idx = 3 | | ring[0] = 0 | | ring[1] = 1 | | ring[2] = 2 | +———————-+ +———————-+ | Used Ring | device 写 +———————-+ | idx = 2 | | ring[0] = 0 | | ring[1] = 1 | +———————-+

过程图

guest:

  1. 填 desc[0], desc[1], desc[2]
  2. 把 0,1,2 放进 avail ring
  3. 通知 device device:
  4. 从 avail ring 看到 0,1,2 可用
  5. 按 desc 去访问 buffer
  6. 做完后把 0,1,2 放进 used ring
  7. 通知 guest

所以 split ring 的感觉就是: Descriptor Table = 货物详情 Avail Ring = 待处理列表 Used Ring = 已处理列表

virtio queue 中的 desc queue 被 avail 和 used queue 索引

virtio,甚至说,设备的工作基本模型是,driver (虚拟机) 提供来提供内存,这些内存填充到 desc table 中。也就是无论 guest 给 device 发消息还是 device 给 guest 发消息,内存都是 guest 来提前准备的。

这三个 queue 都是只有一个 writer ,所以很容易同步。 也就是 guest 来写 desc table 和 avail ring ,而 device 来写 used ring 。

队列大小 4,假设:

真的是这样吗? 关于 last_avail_idx 和 used.idx 的关系为:

假设 guest 一次提交了 4 个请求:

avail.idx = 4
used.idx = 0

device 很快把这 4 个请求都从 avail ring 里读出来了,于是它内部变成:

last_avail_idx = 4

但这 4 个请求还在处理中,只完成了 1 个,于是共享内存里是:

used.idx = 1

这时就出现了:

last_avail_idx = 4
used.idx = 1

补充一下,那么如果 device 想要给 guest 发送消息,那么是什么样子的? qemu 中 virtio balloon 有一个经典的例子,也就是说,有一段内存本来就是共享的, 然后发送中断就可以了:

static void virtio_balloon_free_page_stop(VirtIOBalloon *s)
{
    VirtIODevice *vdev = VIRTIO_DEVICE(s);

    if (s->free_page_hint_status != FREE_PAGE_HINT_S_STOP) {
        /*
         * The lock also guarantees us that the
         * virtio_ballloon_get_free_page_hints exits after the
         * free_page_hint_status is set to S_STOP.
         */
        qemu_mutex_lock(&s->free_page_lock);
        /*
         * The guest isn't done hinting, so send a notification
         * to the guest to actively stop the hinting.
         */
        s->free_page_hint_status = FREE_PAGE_HINT_S_STOP;
        qemu_mutex_unlock(&s->free_page_lock);
        virtio_notify_config(vdev);
    }
}

内核中的响应函数:

void virtio_config_changed(struct virtio_device *dev)
{
	unsigned long flags;

	spin_lock_irqsave(&dev->config_lock, flags);
	__virtio_config_changed(dev);
	spin_unlock_irqrestore(&dev->config_lock, flags);
}

内核数据结构:

以 virtio-blk 为例,如果从 multiqueue 到达了一个消息下来,那么 vring_avail 增加一个。 如果在 host 中间将任务完成了,那么 vring_used 增加一个, 在 host 通过中断的方式通知 guest 之后,guest 处理 vring_used ,并且释放

进一步的细节:

QEMU 数据结构 VirtQueue

这里的,VRing 包含 VRingAvail VRingDesc VRingUsed 三个结构体是有一些技巧的 需要考虑地址空间的问题。

VirtQueueElement

typedef struct VirtQueueElement
{
    unsigned int index;
    unsigned int len;
    unsigned int ndescs;
    unsigned int out_num;
    unsigned int in_num;
    /* Element has been processed (VIRTIO_F_IN_ORDER) */
    bool in_order_filled;
    hwaddr *in_addr;
    hwaddr *out_addr;
    struct iovec *in_sg;
    struct iovec *out_sg;
} VirtQueueElement;

VirtQueueElement 是对于 VRingDesc 中元素的封装。细节如下:

  1. virtqueue_split_pop 中 通过 vring_split_desc_read 和 virtqueue_split_read_next_desc 将链表构建起来的 desc 转换到 VirtQueueElement 的数组中。
  2. virtio_scsi_handle_cmd_vq 中 先调用 virtio_scsi_pop_req -> virtqueue_split_pop 组装获取到 VirtIOSCSIReq (其中第一个元素是 VirtQueueElement) 然后通过 virtio_scsi_handle_cmd_req_submit 真的去处理

VirtQueueElement::in_num 和 VirtQueueElement::out_num 是什么东西

hw/virtio/virtio-balloon.c 中的 get_free_page_hints 为例子:

if (elem->out_num) {
    uint32_t id;
    ...
}

意思是:

- 这个 vring 元素里带了一个 guest -> device 的输出 buffer
- QEMU 从 out_sg 里读一个 uint32_t id
- 这个 id 是 free-page-hint 的 command/session id
- 如果这个 id 和当前 host 在 config 里发布的 free_page_hint_cmd_id 一致,就说明 guest 正在响应这一次 hint 请求,于是把状态从 REQUESTED 切到 START

if (elem->in_num && dev->free_page_hint_status == FREE_PAGE_HINT_S_START) {
    for (i = 0; i < elem->in_num; i++) {
        qemu_guest_free_page_hint(elem->in_sg[i].iov_base,
                                  elem->in_sg[i].iov_len);
    }
}

意思是:

- 这个 vring 元素里带了若干个 device 可访问的 in buffer
- 对 free-page-hint 这个队列来说,这些 in buffer 不是“device 要回写结果”,而是 guest 把“这些页是 free page”的内存段挂出来,让 host 直接看
- QEMU 对每个 in_sg[i] 调 qemu_guest_free_page_hint(),把这段 guest 内存对应的页从 migration dirty bitmap 里清掉

qemu 提交元素

virtio packed ring 结构

先找文档,解决为什么问题,相比较而言,差别是什么?

核心的结构体

virtio_config_ops

static const struct virtio_config_ops virtio_pci_config_ops = {
  .get    = vp_get,
  .set    = vp_set,
  .generation = vp_generation,
  .get_status = vp_get_status,
  .set_status = vp_set_status,
  .reset    = vp_reset,
  .find_vqs = vp_modern_find_vqs,
  .del_vqs  = vp_del_vqs,
  .get_features = vp_get_features,
  .finalize_features = vp_finalize_features,
  .bus_name = vp_bus_name,
  .set_vq_affinity = vp_set_vq_affinity,
  .get_vq_affinity = vp_get_vq_affinity,
  .get_shm_region  = vp_get_shm_region,
};

细节

  1. 勉强看看 https://github.com/rust-vmm/vm-virtio/blob/main/virtio-queue/README.md

  2. 原来文档就没这么点啊

    • https://docs.oasis-open.org/virtio/virtio/v1.3/virtio-v1.3.html

vring_size

参数 align 为 :

/* The alignment to use between consumer and producer parts of vring.
 * x86 pagesize again. */
#define VIRTIO_PCI_VRING_ALIGN    4096

参数 num 是 VIRTIO_PCI_QUEUE_NUM, kvmtool 配置的是 16

static inline unsigned vring_size(unsigned int num, unsigned long align)
{
  return ((sizeof(struct vring_desc) * num + sizeof(__virtio16) * (3 + num)
     + align - 1) & ~(align - 1))
    + sizeof(__virtio16) * 3 + sizeof(struct vring_used_elem) * num;
}

desc

vring_avail 表示当前设备可以使用的 vring_desc, 这些数据从上层到达之后,vring_avail::idx++, vring_avail::ring 描述的项目增加,如果被设备消费了,那么 last_avail_idx ++

设备每处理一个可用描述符数组 ring 的描述符链,都需要将其追加到 vring_used 数组中。

设备通过 vring_used 将告诉驱动那些数据被使用了。感觉这就是一个返回值列表罢了。

vring_used_elem::len 是什么作用的

#0  virtqueue_get_buf_ctx_split (ctx=0x0 <fixed_percpu_data>, len=0xffffc90000003f1c, _vq=0xffff888140418e00) at drivers/virtio/virtio_ring.c:790
#1  virtqueue_get_buf_ctx (_vq=0xffff888140418e00, len=len@entry=0xffffc90000003f1c, ctx=ctx@entry=0x0 <fixed_percpu_data>) at drivers/virtio/virtio_ring.c:2282
#2  0xffffffff81722f27 in virtqueue_get_buf (_vq=<optimized out>, len=len@entry=0xffffc90000003f1c) at drivers/virtio/virtio_ring.c:2288
#3  0xffffffff819766ba in virtblk_done (vq=0xffff888140418e00) at drivers/block/virtio_blk.c:283
#4  0xffffffff81722f85 in vring_interrupt (irq=<optimized out>, _vq=<optimized out>) at drivers/virtio/virtio_ring.c:2462
#5  vring_interrupt (irq=<optimized out>, _vq=<optimized out>) at drivers/virtio/virtio_ring.c:2437
#6  0xffffffff811658d1 in __handle_irq_event_percpu (desc=desc@entry=0xffff888140458400) at kernel/irq/handle.c:158
#7  0xffffffff81165a7f in handle_irq_event_percpu (desc=0xffff888140458400) at kernel/irq/handle.c:193
#8  handle_irq_event (desc=desc@entry=0xffff888140458400) at kernel/irq/handle.c:210
#9  0xffffffff81169e0a in handle_edge_irq (desc=0xffff888140458400) at kernel/irq/chip.c:819
#10 0xffffffff810b9a14 in generic_handle_irq_desc (desc=0xffff888140458400) at include/linux/irqdesc.h:158
#11 handle_irq (regs=<optimized out>, desc=0xffff888140458400) at arch/x86/kernel/irq.c:231
#12 __common_interrupt (regs=<optimized out>, vector=36) at arch/x86/kernel/irq.c:250
#13 0xffffffff81efa443 in common_interrupt (regs=0xffffc90001027998, error_code=<optimized out>) at arch/x86/kernel/irq.c:240

细节

  1. 如何理解 virtio config
    • 之前一直以为是类似 PCI 配置空间,但是似乎不是
      • qemu 中 virtio_balloon_get_config 是做什么的
  2. 为什么感觉两个 config 是不一样的
    • 一个 balloon 的 config
    • 一个是 PCI 的 config
  3. 如何理解 virtio_rmb

  4. 整理一下 https://www.cnblogs.com/LoyenWang/p/14589296.html

  5. 显然,用这个去理解 virtio 就是很好的,这种测试工具 tools/virtio/

  6. 这个东西很有想象力 drivers/net/caif/caif_virtio.c

  7. 使用 bcc 的工具 virtiostat

  8. qemu : 三个设备之间的关系 virtio-blk-pci vs virtio-blk-device vs virtio-blk 显示的是 vda,所以 virtio-blk-pci 应该和 -drive 中 if=virtio 等价吧

  9. 有办法把提供的 virtio-blk 是基于 mmio 的吗?
    • 可以的,需要看看如何配置

如何理解这里的 Device-independent features

https://github.com/rcore-os/virtio-drivers

xen 也是需要搞 iommu 么? drivers/xen/grant-dma-iommu.c

然后找到了这个,看来是需要 nvme 启动的? https://lists.xen.org/archives/html/xen-devel/2022-06/msg00820.html

如何理解 vq[i].vring.num

一个 vq 一个 vring ,还是一个 vq 有多个 vring ? 也就是 multiqueue 的实现是通过构建多个 VirtQueue 实现的?

static void virtio_memory_listener_commit(MemoryListener *listener)
{
    VirtIODevice *vdev = container_of(listener, VirtIODevice, listener);
    int i;

    for (i = 0; i < VIRTIO_QUEUE_MAX; i++) {
        if (vdev->vq[i].vring.num == 0) {
            break;
        }
        virtio_init_region_cache(vdev, i);
    }
}

这个结构体真的让人陌生啊

typedef struct VirtIOBlockReq {
    VirtQueueElement elem;
    int64_t sector_num;
    VirtIOBlock *dev;
    VirtQueue *vq;
    IOVDiscardUndo inhdr_undo;
    IOVDiscardUndo outhdr_undo;
    struct virtio_blk_inhdr *in;
    struct virtio_blk_outhdr out;
    QEMUIOVector qiov;
    size_t in_len;
    struct VirtIOBlockReq *next;
    struct VirtIOBlockReq *mr_next;
    BlockAcctCookie acct;
} VirtIOBlockReq;

有趣 https://blog.stephenmarz.com/2020/11/11/risc-v-os-using-rust-graphics/ 在自己的 os 中实现一个 vring 的结构

注意,hmp 中存在如下命令

info virtio  -- List all available virtio devices
info virtio-queue-element path queue [index] -- Display element of a given virtio queue
info virtio-queue-status path queue -- Display status of a given virtio queue
info virtio-status path -- Display status of a given virtio device
info virtio-vhost-queue-status path queue -- Display status of a given vhost queue

VirtQueueElement

本站所有文章转发 CSDN 将按侵权追究法律责任,其它情况随意。