virtio详细介绍
virtio分为driver和device,driver部分运行于guest操作系统中,device部分运行于hypervisor中,driver和device是生产者和消费者模式动作,driver生产内存,device消费内存。不同virtio版本之间是互相兼容的,driver和device版本不同也可以互相运转。
协议简介
virtio协议标准最早由IBM提出,virtio作为一套标准协议现在有专门的技术委员会进行管理, 开发者可以向技术委员会提供新的virtio设备提案(RFC
),经过委员会通过后可以增加新的virtio设备类型。
组成一个virtio设备的四要素包括: 设备状态域,feature bits
,设备配置空间,一个或者多个virtqueue
。 其中设备状态域包含6种状态:
- ACKNOWLEDGE(1):GuestOS发现了这个设备,并且认为这是一个有效的virtio设备;
- DRIVER (2) : GuestOS知道该如何驱动这个设备;
- FAILED (128) : GuestOS无法正常驱动这个设备,Something is wriong;
- FEATURES_OK (8) : GuestOS认识所有的feature,并且feature协商一完成;
- DRIVER_OK (4) : 驱动load完成,设备可以投入使用了;
- DEVICE_NEEDS_RESET (64) :设备触发了错误,需要重置才能继续工作。
feature bits
用来标志设备支持那个特性,其中bit0-bit23是特定设备可以使用的feature bits
, bit24-bit37预给队列和feature协商机制,bit38以上保留给未来其他用途。 例如:对于virtio-net设备而言,feature bit0表示网卡设备支持checksum校验。 VIRTIO_F_VERSION_1
这个feature bit用来表示设备是否支持virtio 1.0 spec标准。
在virtio协议中,所有的设备都使用virtqueue来进行数据的传输。 每个设备可以有0个或者多个virtqueue,每个virtqueue占用2个或者更多个4K的物理页。 virtqueue有Split Virtqueues
和Packed Virtqueues
两种模式, 在Split virtqueues
模式下virtqueue被分成若干个部分, 每个部分都是前端驱动或者后端单向可写的(不能两端同时写)。 每个virtqueue都有一个16bit的queue size参数,表示队列的总长度。 每个virtqueue由3个部分组成:
+-------------------+--------------------------------+-----------------------+
| Descriptor Table | Available Ring (padding) | Used Ring |
+-------------------+--------------------------------+-----------------------+
- Descriptor Table:存放IO传输请求信息;
- Available Ring:记录了Descriptor Table表中的哪些项被更新了,前端Driver可写但后端只读;
- Used Ring:记录Descriptor Table表中哪些请求已经被提交到硬件,前端Driver只读但后端可写。
整个virtio协议中设备IO请求的工作机制可以简单地概括为:
- 前端驱动将IO请求放到
Descriptor Table
中,然后将索引更新到Available Ring
中,然后kick后端去取数据; - 后端取出IO请求进行处理,然后结果刷新到
Descriptor Table
中再更新Using Ring
,然后发送中断notify前端。
从virtio协议可以了解到virtio设备支持3种设备呈现模式:
- Virtio Over PCI BUS,依旧遵循PCI规范,挂在到PCI总线上,作为virtio-pci设备呈现;
- Virtio Over MMIO,部分不支持PCI协议的虚拟化平台可以使用这种工作模式,直接load到系统总线上;
- Virtio Over Channel I/O:主要用在s390平台上,virtio-ccw使用这种基于channel I/O的机制。
virtio主要cap
对于新的virtio modern
,协议将配置结构划分为5种:
/* Common configuration */
#define VIRTIO_PCI_CAP_COMMON_CFG 1
/* Notifications */
#define VIRTIO_PCI_CAP_NOTIFY_CFG 2
/* ISR Status */
#define VIRTIO_PCI_CAP_ISR_CFG 3
/* Device specific configuration */
#define VIRTIO_PCI_CAP_DEVICE_CFG 4
/* PCI configuration access */
#define VIRTIO_PCI_CAP_PCI_CFG 5
以上的每种配置结构是直接映射到virtio设备的BAR空间内,那么如何指定每种配置结构的位置呢? 答案是通过PCI Capability list
方式去指定,这和物理PCI设备是一样的,体现了virtio-pci的协议兼容性。
struct virtio_pci_cap {
u8 cap_vndr; /* Generic PCI field: PCI_CAP_ID_VNDR */
u8 cap_next; /* Generic PCI field: next ptr. */
u8 cap_len; /* Generic PCI field: capability length */
u8 cfg_type; /* Identifies the structure. */
u8 bar; /* Where to find it. */
u8 padding[3]; /* Pad to full dword. */
le32 offset; /* Offset within bar. */
le32 length; /* Length of the structure, in bytes. */
};
只是略微不同的是,virtio-pci的Capability有一个统一的结构, 其中cfg_type
表示Cap的类型,bar表示这个配置结构被映射到的BAR空间号。 这样每个配置结构都可以通过BAR空间直接访问,或者通过PCI配置空间的VIRTIO_PCI_CAP_PCI_CFG
域进行访问。 每个Cap的具体结构定义可以参考virtio spec 4.1.4.3小节。