共计 2569 个字符,预计需要花费 7 分钟才能阅读完成。
这篇文章将为大家详细讲解有关 virtio 驱动是如何同设备交互,文章内容质量较高,因此丸趣 TV 小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。
irtio 是对虚拟化环境下 guest kernel 中 io 操作的一种优化。
首先需要说明的是,在内核的角度来看,virtio 设备及其 driver,和其他设备及驱动一样,都是普通的设备,并没有什么特殊性。也就是说,内核并不知道这种 io 优化的存在。
virtio 设备,在系统层面看,就是 pci 设备。但是,为了提高 io 效率,对 io 操作做出了优化。
主要方案是:
1) 当 virtio 设备输出数据时,driver 将数据送到 buffer 队列中(从 virtio 网卡驱动的代码来看,此操作无内存拷贝,直接将数据所占的内存 作为 buffer 添加到队列中就完成了),然后通过 io 指令写设备寄存器(vp_dev- ioaddr + VIRTIO_PCI_QUEUE_NOTIFY),以通知虚拟机系统(kvm+qemu)。虚拟机系统捕获了 io 指令,就得到了通知,从 buffer 队 列中获取设备输出的数据。
2) 当需要向 virtio 设备输入数据时,虚拟机系统将数据送到 buffer 队列中,然后触发设备中断。driver 收到中断后,直接从队列中取出数据即可 (从 virtio 网卡驱动的代码来看,队列中的数据已经不需要再进行内存拷贝,队列中的数据已经是 sk_buff 结构了)。
从上面的机制来看,virtio 并不是完全没有了 io 操作。例如,设备输出数据时,在将数据送入 buffer 队列后,还是执行了 io 操作,以通知虚拟机系统。但是,这个 io 操作,并不是将输出数据写入设备,而是将数据已入队这件事,写入设备。
以上是 virtio 的大体原理。下面来看看 virtio 的设计思路。
大体分如下 4 个层次。
一、buffer 队列
既然 virtio 通过 buffer 队列实现设备输入输出。那么,如果每一种设备都来实现一下 buffer 队列,不是浪费么?没错,virtio 考虑到这个 共性需求,因此就实现一个共同的 buffer 队列模块——virtio_ring(一个环型队列)。但是,如果哪天 buffer 队列的实现,需要重新设计 怎么办?考虑到这一点,再对 buffer 队列的操作包装出一个抽象层——struct virtqueue_ops。每一种 buffer 队列的实现,只要提供一个 virtqueue_ops 结构变量给用户使用即可。这就实现了队列操作与队列 实现的解耦。
struct virtqueue_ops
{
int (*add_buf)(struct virtqueue *vq,
struct scatterlist sg[],
unsigned int out_num,
unsigned int in_num,
void *data);
void (*kick)(struct virtqueue *vq);
void *(*get_buf)(struct virtqueue *vq, unsigned int *len);
void (*disable_cb)(struct virtqueue *vq);
bool (*enable_cb)(struct virtqueue *vq);
};
二、pci 层
每一个 virtio 设备(例如:块设备或网卡),在系统层面看来,都是一个 pci 设备。这些设备之间,有共性部分,也有差异部分。
1) 共性部分:这些设备都需要挂接相应的 buffer 队列操作 virtqueue_ops,都需要申请若干个 buffer 队列,当执行 io 输出时,需要向 队列写入数据;都需要执行 pci_iomap 将设备配置寄存器区间映射到内存区间;都需要设置中断处理;等中断来了,都需要从队列读出数据,并通知虚拟机 系统,数据已入队。
2) 差异部分:设备中系统中,如何与业务关联起来。各个设备不相同。例如,网卡在内核中是一个 net_device,与协议栈系统关联起来。同时,向队列中写入什么数据,数据的含义如何,各个设备不相同。队列中来了数据,是什么含义,如何处理,各个设备不相同。
如果每个 virtio 设备都完整实现自己的功能,又会形成浪费。
针对这个现象,virtio 又设计了 virtio_pci 模块,以处理所有 virtio 设备的共性部分。这样一来,所有的 virtio 设备,在系统层面看来,都是一个 pci 设备,其设备驱动都是 virtio_pci。
但是,virtio_pci 并不能完整的驱动任何一个设备。因此,virtio_pci 在 probe(接管)每一个设备时,根据每个 pci 设备的 subsystem vendor/device id 来识别出这具体是哪一种 virtio 设备,然后相应的向内核注册一个 virtio 设备。当然,在注册 virtio 设备之前,virtio_pci 驱动 已经为此设备做了诸多共性的操作。同时,还为设备提供了各种操作的适配接口,例如,一些常用的 pci 设备操作,还有申请 buffer 队列的操作。这些操 作,都通过 virtio_config_ops 结构变量来适配。
三、virtio 驱动
这里讲 virtio 驱动,指的是具体的各个设备的驱动了。例如,网卡或块设备。有了前面所述的各模块的工作,virtio 各个设备的驱动实现,就相对简单了。大体来说,除了完成本设备特有的功能以外,剩下的基本就是 buffer 队列相关操作了。
那就是申请几个队列,并提供相应的回调函数。有数据要输出,往队列中送就行了。队列来数据了,自然会有中断产生,中断处理中,自然会触发回调来处理。
四、virtio_bus
内核中的各种对象,总是有秩序的。为了管理每种具体的 virtio 驱动及每个具体的 virtio 设备,干脆搞了一个 virtio_bus 出来。当然,这个 bus 并不存在实际的硬件电路,纯粹起个管理与适配作用。就这个管理与适配功能而言,他和 pci 总线是相似的。全部的 virtio driver 与 virtio device,在 virtio_bus 中都能够找到。每当有新的 virtio driver 或者 virtio device 注册到系统中时,系统都会执行一次设备与驱动的匹配操作。
关于 virtio 驱动是如何同设备交互 就分享到这里了,希望以上内容可以对大家有一定的帮助,可以学到更多知识。如果觉得文章不错,可以把它分享出去让更多的人看到。