首页 | 新闻 | 新品 | 文库 | 方案 | 视频 | 下载 | 商城 | 开发板 | 数据中心 | 座谈新版 | 培训 | 工具 | 博客 | 论坛 | 百科 | GEC | 活动 | 主题月 | 电子展
返回列表 回复 发帖

Virtio 基本概念和设备操作-2

Virtio 基本概念和设备操作-2

virtio 设备配置的操作针对 virtio 设备配置的操作主要包括四个部分——读写特征位,读写配置空间,读写状态位,重启设备,如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct virtio_config_ops {
    void (*get)(struct virtio_device *vdev, unsigned offset,void *buf, unsigned len);
    void (*set)(struct virtio_device *vdev, unsigned offset, const void *buf, unsigned len);
    u8 (*get_status)(struct virtio_device *vdev);
    void (*set_status)(struct virtio_device *vdev, u8 status);
    void (*reset)(struct virtio_device *vdev);
    int (*find_vqs)(struct virtio_device *, unsigned nvqs,
            struct virtqueue *vqs[],
            vq_callback_t *callbacks[],
            const char *names[]);
    void (*del_vqs)(struct virtio_device *);
    u32 (*get_features)(struct virtio_device *vdev);
    void (*finalize_features)(struct virtio_device *vdev);
    const char *(*bus_name)(struct virtio_device *vdev);
};




get():读取某配置域的值
set():设置写入某配置域
get_status():读取状态位
set_status():写入状态位
reset():重启设备主要是清除状态位并重新配置,还有清除掉所有的中断及其回调。当然也可以用于 guest 恢复驱动。
get_features():读取 feature bits
finalize_features():确认最终使用的特征并写入 Guest 特征位
Virtqueue每个设备拥有多个 virtqueue 用于大块数据的传输。virtqueue 是一个简单的队列,guest 把 buffers 插入其中,每个 buffer 都是一个分散-聚集数组。驱动调用 find_vqs()来创建一个与 queue 关联的结构体。virtqueue 的数目根据设备的不同而不同,比如 block 设备有一个 virtqueue,network 设备有 2 个 virtqueue,一个用于发送数据包,一个用于接收数据包。Balloon 设备有 3 个 virtqueue.
针对 virtqueue 的操作包括:
1)
1
2
3
4
5
6
7
.int virtqueue_add_buf(
              struct virtqueue *_vq,
              struct scatterlist sg[],
              unsigned int out,
              unsigned int in,
              void *data,
              gfp_t gfp)




add_buf()用于向 queue 中添加一个新的 buffer,参数 data 是一个非空的令牌,用于识别 buffer,当 buffer 内容被消耗后,data 会返回。
2).virtqueue_kick():
Guest 通知 host 单个或者多个 buffer 已经添加到 queue 中,调用 virtqueue_notify(),notify 函数会向 queue notify(VIRTIO_PCI_QUEUE_NOTIFY)寄存器写入 queue index 来通知 host。
3).void *virtqueue_get_buf(struct virtqueue *_vq, unsigned int *len)
返回使用过的 buffer,len 为写入到 buffer 中数据的长度。获取数据,释放 buffer,更新 vring 描述符表格中的 index。
4).virtqueue_disable_cb()
示意 guest 不再需要再知道一个 buffer 已经使用了,也就是关闭 device 的中断。驱动会在初始化时注册一个回调函数,disable_cb()通常在这个 virtqueue 回调函数中使用,用于关闭再次的回调发生。
5).virtqueue_enable_cb()
与 disable_cb()刚好相反,用于重新开启设备中断的上报。
Vringvirtio_ring 是 virtio 传输机制的实现,vring 引入 ring buffers 来作为我们数据传输的载体。
virtio_ring 包含 3 部分:
描述符数组(descriptor table)用于存储一些关联的描述符,每个描述符都是一个对 buffer 的描述,包含一个 address/length 的配对。
可用的 ring(available ring)用于 guest 端表示那些描述符链当前是可用的。
使用过的 ring(used ring)用于表示 Host 端表示那些描述符已经使用。
Ring 的数目必须是 2 的次幂。
描述符和描述符表格vring descriptor 用于指向 guest 使用的 buffer。
addr:guest 物理地址
len:buffer 的长度
flags:flags 的值含义包括:
  • VRING_DESC_F_NEXT:用于表明当前 buffer 的下一个域是否有效,也间接表明当前 buffer 是否是 buffers list 的最后一个。
  • VRING_DESC_F_WRITE:当前 buffer 是 read-only 还是 write-only。
  • VRING_DESC_F_INDIRECT:表明这个 buffer 中包含一个 buffer 描述符的 list
next:所有的 buffers 通过 next 串联起来组成 descriptor table
多个 buffer 组成一个 list 由 descriptor table 指向这些 list。
约定俗成,每个 list 中,read-only buffers 放置在 write-only buffers 前面。
图    2.descriptor tableIndirect Descriptors有些设备可能需要同时完成大量数据传输的大量请求,设备 VIRTIO_RING_F_INDIRECT_DESC 特性能够满足这种需求。为了增加 ring 的容量,vring 可以指向一个可以处于内存中任何位置 indirect descriptors table,而这个 table 指向一组 vring descriptors,而这些 vring descriptor 分别指向一组 buffer list(如图所示)。当然 indirect descriptors table 中的 descriptor 不能再次指向 indirect descriptors table。单个 indirect descriptor table 可以包含 read-only 和 write-only 的 descriptors,带有 write-only flag 的 descriptor 会被忽略。
图    3.indirect decriptorsAvailable RingAvailable ring 指向 guest 提供给设备的描述符,它指向一个 descriptor 链表的头。Available ring 结构如下图所示。其中标识 flags 值为 0 或者 1,1 表明 Guest 不需要 device 使用完这些 descriptor 时上报中断。idx 指向我们下一个 descriptor 入口处,idx 从 0 开始,一直增加,使用时需要取模:
idx=idx&(vring.num-1)
图 4.available    ringUsed RingUsed ring 指向 device(host)使用过的 buffers。Used ring 和 Available ring 之间在内存中的分布会有一定间隙,从而避免了 host 和 guest 两端由于 cache 的影响而会写入到 virtqueue 结构体的同一部分的情况。
flags 用于 device 告诉 guest 再次添加 buffer 到 available ring 时不再提醒,也就是说 guest 添加 buffers 到 available ring 时不必进行 kick 操作。
Used vring element 包含 id 和 len,id 指向 descriptor chain 的入口,与之前 guest 写入到 available ring 的入口项一致。
len 为写入到 buffer 中的字节数。
1
2
3
4
5
6
struct vring_used_elem {
    /* Index of start of used descriptor chain. */
    __u32 id;
    /* Total length of the descriptor chain which was used (written to) */
    __u32 len;
};




Virtio 设备操作设备的初始化1. 重启设备状态,状态位写入 0
2. 设置状态为 ACKNOWLEDGE,guest(driver)端当前已经识别到了设备
3. 设置状态为 Driver,guest 知道如何驱动当前设备
4. 设备特定的安装和配置:特征位的协商,virtqueue 的安装,可选的 MSI-X 的安装,读写设备专属的配置空间等
5. 设置状态为 Driver_OK 或者 Failed(如果中途出现错误)
6. 当前设备初始化完毕,可以进行配置和使用
设备的安装和配置设备操作包括两个部分:driver(guest)提供 buffers 给设备,处理 device(host)使用过的 buffers。
初始化 virtqueue该部分代码的实现在 virtio-pci.c 里 setup_vps()里面,具体为:
1.选择 virtqueue 的索引,写入 Queue Select 寄存器
2.读取 queue size 寄存器获得 virtqueue 的可用数目
3.分配并清零 4096 字节对齐的连续物理内存用于存放 virtqueue(调用 alloc_pages_exact()).把内存地址除以 4096 写入 Queue Address 寄存器(VIRTIO_PCI_QUEUE_ADDR_SHIFT)
4.可选情况下,如果 MSI-X 中断机制启用,选择一个向量用于 virtqueue 请求的中断,把对应向量的 MSI-X 表格入口号写入 Queue Vector 寄存器域,然后再次读取该域以确认返回正确值。
Virtqueue 所需要的字节数由下面的公式获得:
1
2
3
((sizeof(struct vring_desc) * num
+ sizeof(__u16) * (3 + num) + align - 1) & ~(align – 1))
+ sizeof(__u16) * 3 + sizeof(struct vring_used_elem) * num




其中,num 为 virtqueue 的数目。
Guest 向设备提供 buffer1.把 buffer 添加到 description table 中,填充 addr,len,flags
2.更新 available ring head
3.更新 available ring 中的 index
4.通知 device,通过写入 virtqueue index 到 Queue Notify 寄存器
Device 使用 buffer 并填充 used ringdevice 端使用 buffer 后填充 used ring 的过程如下:
1.virtqueue_pop()——从描述符表格(descriptor table)中找到 available ring 中添加的 buffers,映射内存
2.从分散-聚集的 buffer 读取数据
3.virtqueue_fill()——取消内存映射,更新 ring[idx]中的 id 和 len 字段
4.virtqueue_flush()——更新 vring_used 中的 idx
5.virtio_notify()——如果需要的话,在 ISR 状态位写入 1,通知 guest 描述符已经使用
中断处理在 MSI-X 关闭的情况下,设备端会设置 ISR bit lower bit 并发送 PCI 中断给客户机,
客户机端会读取 ISR lower bit,同时会清零 ,如果 lower bit 为 0,则无中断.
如果有中断,遍历一遍该设备每个 virtqueue 上的 used rings,来判断是否有中断服务需要处理。
在 MSI-X 开启的情况下,设备端会为设备请求一个 MSI-X interrupt message 并设置 Queue Vector 寄存器的值为 MSI-X table entry,如果 Queue Vector 为 NO_VECTOR,不再请求 interrupt message。
客户机端会遍历映射到该 MSI-X vector 每个 virtqueue 上的 used rings,来判断是否有中断服务需要处理。
Config Changed设备端如果改变其 configure space,也存在两种情况。
客户机端会在 MSI-X 关闭的情况下,读取 ISR 高位,判断是否为 1,扫描所有 virtqueue 上的 used rings,触发驱动对 config changed 的处理函数。在 MSI-X 开启的情况下,与中断相同,同样请求 MSI-X interrupt message,将 Configuration Vector 设置为 MSI-X table entry。
总结virtio 是 KVM 虚拟化技术中 IO 虚拟化的一个重要框架,现在有很多虚拟设备都使用了 virtio。本文着重介绍了 virtio 的基本概念和设备操作,可以方便读者更深一层地理解 virtio,同时也对 virtio 感兴趣的朋友能够从本文中获取帮助。
返回列表