zoukankan      html  css  js  c++  java
  • 转载 Linux虚拟化KVM-Qemu分析virtqueue

    1. 概述

    • 前边系列将Virtio Device和Virtio Driver都已经讲完,本文将分析virtqueue;
    • virtqueue用于前后端之间的数据交换,一看到这种数据队列,首先想到的就是ring-buffer,实际的实现会是怎么样的呢?

    2. 数据结构

    先看一下核心的数据结构:

    • 通常Virtio设备操作Virtqueue时,都是通过struct virtqueue结构体,这个可以理解成对外的一个接口,而Virtqueue机制的实现依赖于struct vring_virtqueue结构体;
    • Virtqueue有三个核心的数据结构,由struct vring负责组织:
      1. struct vring_desc:描述符表,每一项描述符指向一片内存,内存类型可以分为out类型和in类型,分别代表输出和输入,而内存的管理都由驱动来负责。该结构体中的next字段,可用于将多个描述符构成一个描述符链,而flag字段用于描述属性,比如只读只写等;
      2. struct vring_avail:可用描述符区域,用于记录设备可用的描述符ID,它的主体是数组ring,实际就是一个环形缓冲区;
      3. struct vring_used:已用描述符区域,用于记录设备已经处理完的描述符ID,同样,它的ring数组也是环形缓冲区,与struct vring_avail不同的是,它还记录了设备写回的数据长度;

    这么看,当然是有点不太直观,所以,下图来了:

    • 简单来说,驱动会分配好内存(scatterlist),并通过virtqueue_add添加到描述表中,这样描述符表中的条目就都能对应到具体的物理地址了,其实可以把它理解成一个资源池子;
    • 驱动可以将可用的资源更新到struct vring_avail中,也就是将可用的描述符ID添加到ring数组中,熟悉环形缓冲区的同学应该清楚它的机制,通过维护头尾两个指针来进行管理,Driver负责更新头指针(idx),Device负责更新尾指针(Qemu中的Device负责维护一个last_avail_idx),头尾指针,你追我赶,生生不息;
    • 当设备使用完了后,将已用的描述符ID更新到struct vring_used中,vring_virtqueue自身维护了last_used_idx,机制与struct vring_avail一致;

    3. 流程分析

    3.1 发送

    当驱动需要把数据发送给设备时,流程如上图所示:

    1. ①A表示分配一个Buffer并添加到Virtqueue中,①B表示从Used队列中获取一个Buffer,这两种中选择一种方式;
    2. ②表示将Data拷贝到Buffer中,用于传送;
    3. ③表示更新Avail队列中的描述符索引值,注意,驱动中需要执行memory barrier操作,确保Device能看到正确的值;
    4. ④与⑤表示Driver通知Device来取数据;
    5. ⑥表示Device从Avail队列中获取到描述符索引值;
    6. ⑦表示将描述符索引对应的地址中的数据取出来;
    7. ⑧表示Device更新Used队列中的描述符索引;
    8. ⑨与⑩表示Device通知Driver数据已经取完了;

    3.2 接收

    当驱动从设备接收数据时,流程如上图所示:

    1. ①表示Device从Avail队列中获取可用描述符索引值;
    2. ②表示将数据拷贝至描述符索引对应的地址上;
    3. ③表示更新Used队列中的描述符索引值;
    4. ④与⑤表示Device通知Driver来取数据;
    5. ⑥表示Driver从Used队列中获取已用描述符索引值;
    6. ⑦表示将描述符索引对应地址中的数据取出来;
    7. ⑧表示将Avail队列中的描述符索引值进行更新;
    8. ⑨与⑩表示Driver通知Device有新的可用描述符;

    3.3 代码分析

    代码的分析将围绕下边这个图来展开(Virtio-Net),偷个懒,只分析单向数据发送了:

    3.3.1 virtqueue创建

    • 之前的系列文章分析过virtio设备和驱动,Virtio-Net是PCI网卡设备驱动,分别会在virtnet-probevirtio_pci_probe中完成所有的初始化;
    • virtnet_probe函数入口中,通过init_vqs完成Virtqueue的初始化,这个逐级调用关系如图所示,最终会调用到vring_create_virtqueue来创建Virtqueue;
    • 这个创建的过程中,有些细节是忽略的,比如通过PCI去读取设备的配置空间,获取创建Virtqueue所需要的信息等;
    • 最终就是围绕vring_virtqueue数据结构的初始化展开,其中vring数据结构的内存分配也都是在驱动中完成,整个结构体都由驱动来管理与维护;

    3.3.2 virtio-net驱动发送

    • 网络数据的传输在驱动中通过start_xmit函数来实现;
    • xmit_skb函数中,sg_init_table初始化sg列表,sg_set_buf将sg指向特定的buffer,skb_to_sgvec将socket buffer中的数据填充sg;
    • 通过virtqueue_add_outbuf将sg添加到Virtqueue中,并更新Avail队列中描述符的索引值;
    • virtqueue_notify通知Device,可以过来取数据了;

    3.3.3 Qemu virtio-net设备接收

    • Guest驱动写寄存器操作时,陷入到KVM中,最终Qemu会捕获到进行处理,入口函数为kvm_handle_io
    • Qemu中会针对IO内存区域设置读写的操作函数,当Guest进行IO操作时,最终触发操作函数的调用,针对Virtio-Net,由于它是PCI设备,操作函数为virtio_pci_config_write
    • virtio_pci_config_write函数中,对Guest的写操作进行判断并处理,比如在VIRTIO_PCI_QUEUE_NOTIFY时,调用virtio_queue_notify,用于处理Guest驱动的通知,并最终回调handle_output函数;
    • 针对Virtio-Net设备,发送的回调函数为virtio_net_handle_tx_bh,并在virtio_net_flush_tx中完成操作;
    • 通用的操作模型:通过virtqueue_pop从Avail队列中获取地址,将数据进行处理,通过virtqueue_push将处理完后的描述符索引更新到Used队列中,通过virtio_notify通知Guest驱动;

    Virtqueue这种设计思想比较巧妙,不仅用在virtio中,在AMP系统中处理器之间的通信也能看到它的身影。
    草草收场了,下回见。

    参考

    https://www.redhat.com/en/blog/virtqueues-and-virtio-ring-how-data-travels

    http代理服务器(3-4-7层代理)-网络事件库公共组件、内核kernel驱动 摄像头驱动 tcpip网络协议栈、netfilter、bridge 好像看过!!!! 但行好事 莫问前程 --身高体重180的胖子
  • 相关阅读:
    UVA 10462 Is There A Second Way Left?(次小生成树&Prim&Kruskal)题解
    POJ 1679 The Unique MST (次小生成树)题解
    POJ 2373 Dividing the Path (单调队列优化DP)题解
    BZOJ 2709 迷宫花园
    BZOJ 1270 雷涛的小猫
    BZOJ 2834 回家的路
    BZOJ 2506 calc
    BZOJ 3124 直径
    BZOJ 4416 阶乘字符串
    BZOJ 3930 选数
  • 原文地址:https://www.cnblogs.com/codestack/p/14828141.html
Copyright © 2011-2022 走看看