virtio 学习随笔 —— 三、virtio 源码分析
三、virtio源码分析
参考文档:
Linux Kernel 之九 详解 virtio-net 源码框架、执行流程-CSDN博客
Introduction to VirtIO
基于Linux版本:v5.7
virtio代码层次
virtio 前端驱动运行在 Guest 中,以 virtio-net 驱动为例,其源码位于linux/driver/net/virtio_net.c
中;virtio 层作为前后端通信的桥梁存在,源码位于linux/drivers/virtio/virtio.c
中;virtio_ring 层作为虚拟传输层的具体实现,代码位于linux/drivers/virtio_ring.c
;virtio 后端既可以在 QEMU(用户态实现,适用于纯软件模拟)中,也可以通过 vhost 在 Linux 内核 中实现(性能更优)。
重要的数据结构
抽象传输层:virtqueue与vring
// include/linux/virtio.h: 13
/**
* virtqueue - a queue to register buffers for sending or receiving.
* @list: the chain of virtqueues for this device
* @callback: the function to call when buffers are consumed (can be NULL).
* @name: the name of this virtqueue (mainly for debugging)
* @vdev: the virtio device this queue was created for.
* @priv: a pointer for the virtqueue implementation to use.
* @index: the zero-based ordinal number for this queue.
* @num_free: number of elements we expect to be able to fit.
*
* A note on @num_free: with indirect buffers, each buffer needs one
* element in the queue, otherwise a buffer will need one element per
* sg element.
*/
struct virtqueue {
struct list_head list;
void (*callback)(struct virtqueue *vq);
const char *name;
struct virtio_device *vdev;
unsigned int index;
unsigned int num_free;
void *priv;
};
virtqueue 是 virtio 框架中的抽象队列,用于在 Guest 和 Hypervisor 之间传递数据。它本身并不关心底层采用何种数据结构来完成数据交换,而是定义了一组统一的接口。通过共享内存机制,Guest 端的 virtio 驱动和 Hypervisor 端的 virtio 设备能够对同一块物理内存进行访问,从而实现高效的数据传输。
vring 则是 virtqueue 的一种具体实现方式,它提供了传输层的实际数据结构和操作逻辑,承担了真正的数据搬运工作。需要注意的是,virtqueue 与 vring 并不是同一个概念:virtqueue 是抽象层,而 vring 是实现层。理论上,virtqueue 也可以由其他数据结构来实现传输层功能,只是目前通用且广泛使用的实现几乎都是基于 vring。
在 virtio 框架中,virtqueue 提供了一组抽象的操作接口,主要包括以下五类函数:
add_buf:向 virtqueue 中添加一个待传输的 buffer(由一组 scatterlist 描述)。
kick:通知对端有新的 buffer 可用,触发数据传输。
get_buf:从 virtqueue 中取出已完成的 buffer,并获取其长度。
disable_cb:临时关闭通知回调,用于批量处理或减少中断。
enable_cb:重新开启通知回调,恢复正常的事件处理。
这五个函数共同构成了 virtqueue 的“传输层操作集”。它们的职责并不是直接搬运数据,而是操作底层的 vring 数据结构(split ring 或 packed ring),来完成 buffer 的提交、消费和事件通知。
换句话说:virtqueue 提供统一接口(抽象 API),上层驱动调用时不用关心底层实现;vring 才是具体实现(传输层),负责描述符队列的组织和内存布局;
对于 virtqueue 和 vring 来说,在内核中涉及以下数据结构:
其中virtqueue
是抽象接口,vring
是底层环形共享内存布局,而 vring_virtqueue
则是基于 vring 的 virtqueue 具体实现,将两者连接起来。
抽象传输层实现:virtio_ring
每个 virtqueue 包含三种 vring,分别为描述符环(descriptor ring)、可用环(available ring)【由驱动操作】和已用环(used ring)【由设备操作】协同工作实现了前后端的高效通信。
- 描述符环
描述符表用于存放 Guest 驱动分配的数据缓冲区的元数据,每个描述符条目指向一个 Guest 驱动分配的数据缓冲区,记录缓冲区的虚拟机物理地址 GPA、长度、可选的下一描述符指针以及标志位用于标识下一描述符是否有效以及当前缓冲区是否只写。
// include/uapi/linux/vritio_ring.h: 90
struct vring_desc
{
__u64 addr;
__u32 len;
__u16 flags;
__u16 next;
};
- 可用环
可用环由一个标志位、一个自由递增的索引和一个指向描述符表索引的数组(每个数组元素对应一个描述符环的描述符链头节点)三部分构成。可用环该数据结构由 Guest Driver 维护,提供给 Hypervisor 中的模拟设备使用。
// include/uapi/linux/vritio_ring.h: 101
struct vring_avail {
__u16 flags;
__u16 idx;
__u16 ring[];
};
┌──────────────┐
│ avail.idx │ (生产者索引,自由递增)
├──────────────┤
│ flags │ (标志位)
├──────────────┤
│ ring[0] │───┐ (描述符链头索引数组)
│ ring[1] │ │
│ ... │ │
└──────────────┘ │
│
│
▼
┌─────────────────────────────────────┐
│ desc[0]: addr=0x1000, len=512 │
│ flags=VRING_DESC_F_NEXT │
│ next=1 │
├─────────────────────────────────────┤
│ desc[1]: addr=0x2000, len=512 │
│ flags=VRING_DESC_F_NEXT │
│ next=2 │
├─────────────────────────────────────┤
│ desc[2]: addr=0x3000, len=512 │
│ flags=0 │
│ next=INVALID │
└─────────────────────────────────────┘
- 已用环
已用环的结构与可用环类似,但由 Hypervisor 中的模拟设备在消费描述符时写入。该数据结构由 Hypervisor 维护,供 Guest 驱动使用。与可用环不同的是,已用环形数组中的每个条目都是由数据对(表示为“已用元素”结构体)组成,这些数据对描述了:
(1) 描述符环形数组中一个描述符(或链式描述符头部)的索引(id),该描述符引用了已被使用(读取或写入)的缓冲区;
(2) 写入描述符缓冲区(或描述符链中所有缓冲区)的总数据长度(len)。
// include/uapi/linux/vritio_ring.h: 108
struct vring_used_elem {
/* Index of start of used descriptor chain. */
__virtio32 id;
/* Total length of the descriptor chain which was used (written to) */
__virtio32 len;
};
struct vring_used
{
__u16 flags;
__u16 idx;
struct vring_used_elem ring[];
};
virtio虚拟总线:virtio_bus
// drivers/virtio/vitio.c: 285
static struct bus_type virtio_bus = {
.name = "virtio",
.match = virtio_dev_match,
.dev_groups = virtio_dev_groups,
.uevent = virtio_uevent,
.probe = virtio_dev_probe,
.remove = virtio_dev_remove,
};
virtio_bus
是一个总线类型描述结构体(struct bus_type
),用于描述 virtio 总线的匹配、探测、移除等操作。该结构体通过函数指针的方式将相应的操作函数(如 .match
、.probe
、.remove
等)注册进内核设备模型中。struct bus_type
定义于内核源码的 include/linux/device/bus.h
文件中。
该结构体中包含多个函数指针字段,其中如virtio_dev_match
和virtio_dev_probe
是较为重要的函数,前者被用于判断设备与驱动是否匹配,即判断virtio_driver
是否适配某个virtio_device
,后者被用于在完成匹配后的执行设备的初始化并将驱动与设备进行绑定。
virtio_device
// include/linux/virtio.h: 92
/**
* virtio_device - representation of a device using virtio
* @index: unique position on the virtio bus
* @failed: saved value for VIRTIO_CONFIG_S_FAILED bit (for restore)
* @config_enabled: configuration change reporting enabled
* @config_change_pending: configuration change reported while disabled
* @config_lock: protects configuration change reporting
* @dev: underlying device.
* @id: the device type identification (used to match it with a driver).
* @config: the configuration ops for this device.
* @vringh_config: configuration ops for host vrings.
* @vqs: the list of virtqueues for this device.
* @features: the features supported by both driver and device.
* @priv: private pointer for the driver's use.
*/
struct virtio_device {
int index;
bool failed;
bool config_enabled;
bool config_change_pending;
spinlock_t config_lock;
struct device dev;
struct virtio_device_id id;
const struct virtio_config_ops *config;
const struct vringh_config_ops *vringh_config;
struct list_head vqs;
u64 features;
void *priv;
};
virtio_device
结构体定义了Linux内核中用于抽象和表示virtio总线上设备的核心数据结构。
virtio_driver
// include/linux/virtio.h: 147
/**
* virtio_driver - operations for a virtio I/O driver
* @driver: underlying device driver (populate name and owner).
* @id_table: the ids serviced by this driver.
* @feature_table: an array of feature numbers supported by this driver.
* @feature_table_size: number of entries in the feature table array.
* @feature_table_legacy: same as feature_table but when working in legacy mode.
* @feature_table_size_legacy: number of entries in feature table legacy array.
* @probe: the function to call when a device is found. Returns 0 or -errno.
* @scan: optional function to call after successful probe; intended
* for virtio-scsi to invoke a scan.
* @remove: the function to call when a device is removed.
* @config_changed: optional function to call when the device configuration
* changes; may be called in interrupt context.
* @freeze: optional function to call during suspend/hibernation.
* @restore: optional function to call on resume.
*/
struct virtio_driver {
struct device_driver driver;
const struct virtio_device_id *id_table;
const unsigned int *feature_table;
unsigned int feature_table_size;
const unsigned int *feature_table_legacy;
unsigned int feature_table_size_legacy;
int (*validate)(struct virtio_device *dev);
int (*probe)(struct virtio_device *dev);
void (*scan)(struct virtio_device *dev);
void (*remove)(struct virtio_device *dev);
void (*config_changed)(struct virtio_device *dev);
#ifdef CONFIG_PM
int (*freeze)(struct virtio_device *dev);
int (*restore)(struct virtio_device *dev);
#endif
};
virtio_driver
结构体定义了一系列用于操作virtio设备的函数指针,这些函数在驱动程序被注册到virtio总线时被调用,用于完成设备的初始化等管理操作。