linux 之 virtio 子系统核心的数据结构
1、 virtio_bus结构
struct bus_type是基于总线驱动设备模型的虚拟总线,定义新的bus,就是填充该结构。virtio_bus定义在drivers/virtio/virtio.c中,具体如下,
注册:
补充注册的早不早:
virtio_bus以core_initcall的方式被注册,该方式注册的启动顺序优先级很高(作为对比,module_init最终对应的是device_initcall),在使用中要注意不同组件的启动顺序。
补充2:virtio_dev_match函数
virtio驱动的match涉及到virtio_device_id结构,在virtio_device结构中包含该结构;在virtio_driver中则是包含该驱动支持的virtio_device_id列表
具体的match流程如下,
可见virtio驱动的match函数先匹配device字段,后匹配vendor字段,二者都满足条件时match成功
补充3:从virtio_dev_match的流程可以看出,virtio_driver中的id_table必须以id->device = 0结尾,以便结束循环
2 virtio_device结构
struct virtio_device定义在include/linux/virtio.h中,具体如下,
2.1 struct virtio_device_id id
其中的device成员标识了当前virtio_device的用途,virt-net是其中的一种,
2.2 const struct virtio_config_ops *config
virtio_config_ops操作集中的函数主要与virtio_device的配置相关,主要有如下2类操作,
- 实例化 / 反实例化virtqueue,其中要特别注意find_vqs函数,该函数用于实例化virtio_device所持有的virtqueue
- . 获取 / 设置virtio_device的属性与状态,相关属性均在虚拟机虚拟出的PCI配置空间
2.3 struct list_head vqs
virtio_device持有的virtqueue链表,virtio-net中建立了2条virtqueue
2.4 u64 features
irtio_driver & virtio_device同时支持的通信特性,也就是前后端最终协商的通信特性
3 virtio_driver结构
struct virtio_driver定义在include/linux/virtio.h中,具体如下,
3.1 const struct virtio_device_id *id_table
对应virtio_device结构中的id成员,virtio_device中标识的是当前device的id属性;而virtio_driver中的id_table则是当前driver支持的所有id列表
3.2 const unsigned int *feature_table & unsigned int feature_table_size
feature列表包含了当前driver支持的所有virtio传输属性,feature_table_size则说明属性数组的元素个数
4 virtqueue结构
struct virtqueue定义在include/linux/virtio.h中,具体如下,
vdev 是引用了 virtio_device 的实例。
5 vring结构
struct vring 定义在include/uapi/linux/virtio_ring.h中,具体如下,
一定要结合一个后端驱动进行分析,可以对照rpmsg-lite分析
可以就分析rpmsg-lite对virtqueue的操作部分,不用上升到rpmsg协议的部分
说明1:vring的三个构成区域
- Destcriptor Table:描述内存buffer,主要包括addr & len等信息
- Avail Ring:用于前端驱动(Guest)通知后端驱动(Host)有可用的描述符
e.g. 前端驱动有一个报文需要发送,需要将其加入Avail Ring,之后通知后端驱动读取
- Used Ring:用于后端驱动(Host)通知前端驱动(Guest)有可用的描述符,或者是后端驱动已将前端驱动提供的描述符使用完毕
e.g. 后端驱动有一个报文需要发送,需要将其加入Used Ring,之后通知前端驱动读取
可见avail & used的命名都是站在Host的角度进行的
说明2:vring的存储
vring结构只是用于描述vring在内存中的布局(因此包含的都是指针变量),实际用于通信的vring是存储在内存中
上文提到的vring的三个区域是在内存中连续存储的,而且是存储在Guest & Host共享的一片连续内存中
我们可以通过vring_init函数理解vring存储结构的布局
实际vring的内存布局如下图所示,
在计算used ring的起始地址时,在avail->ring[num]的地址之后又加了sizeof(__virtio16),也就是增加了2B,是为了容纳avail ring末尾的used_event,该机制详见下文(是一种控制中断触发频率的机制)
说明3:实际vring的大小
实际vring的大小可以通过vring_size函数获得
① 计算avail ring时加3,分别为flags、idx和used_event
② 计算used ring时加3,分别为flags、idx和avail_event
③ 计算过程中,包含了为满足对齐要求padding的空间
说明4:used_event与avail_event机制概述
这2个字段均与virtio设备的VIRTIO_RING_F_EVENT_IDX特性有关,由于virtio驱动触发对方中断将导致CPU反复进出虚拟机 & 宿主机模式,从而降低性能,因此需要控制触发中断频率的机制
① avail ring中的used_event
a. 由前端驱动(Geust)设置,标识希望后端驱动(Host)触发中断的阈值
b. 后端驱动(Host)在向Used Ring加入buffer后,检查Used Ring中的idx字段,只有达到阈值才触发中断
② used_ring中的avail_event
a. 由后端驱动(Host)设置,标识希望前端驱动(Guest)触发中断的阈值
b. 前端驱动(Guest)在向Avail Ring加入buffer后,检查Avail Ring的idx字段,只有达到阈值才触发中断
综上所属,vring结构的构成如下图所示,
6 vring_virtqueue结构
vring_virtqueue结构用于描述前端驱动(Guest)中的一条虚拟队列
总结:virtio_device / vring_virtqueue / virtqueue / vring结构之间的关系