背景
Read the fucking source code!
--By 鲁迅
A picture is worth a thousand words.
--By 高尔基
说明:前端
- KVM版本:5.9.1
- QEMU版本:5.0.0
- 工具:Source Insight 3.5, Visio
概述
- 从本文开始将研究一下virtio;
- 本文会从一个网卡虚拟化的例子来引入virtio,并从大致架构上进行介绍,有个宏观的认识;
- 细节的阐述后续的文章再跟进;
1. 网卡
1.1 网卡工做原理
先来看一下网卡的架构图(以Intel的82540为例):linux

- OSI模型,将网络通讯中的数据流划分为7层,最底下两层为物理层和数据链路层,对应到网卡上就是
PHY
和MAC控制器
;
PHY
:对应物理层,负责通讯设备与网络媒介(网线)之间的互通,它定义传输的光电信号、线路状态等;
MAC控制器
:对应数据链路层,负责网络寻址、错误侦测和改错等;
PHY
和MAC
经过MII/GMII(Media Independent Interface)
和MDIO(Management Data Input/output)
相连;
MII/GMII(Gigabit MII)
:由IEEE
定义的以太网行业标准,与媒介无关,包含数据接口和管理接口,用于网络数据传输;
MDIO
接口,也是由IEEE
定义,一种简单的串行接口,一般用于控制收发器,并收集状态信息等;
- 网卡经过PCI接口接入到PCI总线中,CPU能够经过访问BAR空间来获取数据包,也有网卡直接挂在内存总线上;
- 网卡还有一颗EEPROM芯片,用于记录厂商ID、网卡的MAC地址、配置信息等;
咱们主要关心它的数据流,因此,看看它的工做原理吧:后端

- 网络包的接收与发送,都是典型的
生产者-消费者
模型,简单来讲,CPU会在内存中维护两个ring-buffer
,分别表明RX
和TX
,ring-buffer
中存放的是描述符,描述符里包含了一个网络包的信息,包括了网络包地址、长度、状态等信息;
ring-buffer
有头尾两个指针,发送端为:TDH(Transmit Descriptor Head)和TDT(Transmit Descriptor Tail)
,同理,接收端为:RDH(Receive Descriptor Head)和RDT(Receive Descriptor Tail)
,在数据传输时,由CPU和网卡来分开更新头尾指针的值,这也就是生产者更新尾指针,消费者更新头指针,永远都是消费者追着生产者跑,ring-buffer
也就能转起来了;
- 数据的传输,使用DMA来进行搬运,CPU的拷贝显然是一种低效的选择。在以前PCI系列分析文章中分析过,PCI设备有本身的BAR空间,能够经过DMA在BAR空间和DDR空间内进行搬运;
1.2 Linux网卡驱动
在网卡数据流图中,咱们也基本看到了网卡驱动的影子,驱动与网卡之间是异步通讯:数组

- 驱动程序负责硬件的初始化,以及TX和RX的
ring-buffer
的建立及初始化;
ndo_start_xmit
负责将网络包经过驱动程序发送出去,netif_receive_skb
负责经过驱动程序接收网络包数据;
- 数据经过
struct sk_buff
来存储;
- 发送数据时,CPU负责准备TX网络包数据以及描述符资源,更新TDT指针,并通知NIC能够进行数据发送了,当数据发送完毕后NIC经过中断信号通知CPU进行下一个包的处理;
- 接收数据时,CPU负责准备RX的描述符资源,接收数据后,NIC经过中断通知CPU,驱动程序经过调度内核线程来处理网络包数据,处理完成后进行下一包的接收;
2. 网卡全虚拟化
2.1 全虚拟化方案
全虚拟化方案,经过软件来模拟网卡,Qemu+KVM的方案以下图:网络

- Qemu中,设备的模拟称为前端,好比
e1000
,前端与后端通讯,后端再与底层通讯,咱们来分别看看发送和接收处理的流程;
-
发送:架构
- Guest OS在准备好网络包数据以及描述符资源后,经过写
TDT
寄存器,触发VM的异常退出,由KVM模块接管;
- KVM模块返回到Qemu后,Qemu会检查VM退出的缘由,好比检查到
e1000
寄存器访问出错,于是触发e1000前端
工做;
- Qemu能访问Guest OS中的地址内容,于是
e1000前端
能获取到Guest OS内存中的网络包数据,发送给后端,后端再将网络包数据发送给TUN/TAP驱动,其中TUN/TAP为虚拟网络设备;
- 数据发送完成后,除了更新
ring-buffer
的指针及描述符状态信息外,KVM模块会模拟TX中断;
- 当再次进入VM时,Guest OS看到的是数据已经发送完毕,同时还须要进行中断处理;
- Guest OS跑在vCPU线程中,发送数据时至关于会打算它的执行,直处处理完后再恢复回来,也就是一个严格的同步处理过程;
-
接收:框架
- 当TUN/TAP有网络包数据时,能够经过读取TAP文件描述符来获取;
- Qemu中的I/O线程会被唤醒并触发后端处理,并将数据发送给
e1000前端
;
e1000
前端将数据拷贝到Guest OS的物理内存中,并模拟RX中断,触发VM的退出,并由KVM模块接管;
- KVM模块返回到Qemu中进行处理后,并最终从新进入Guest OS的执行中断处理;
- 因为有I/O线程来处理接收,能与vCPU线程作到并行处理,这一点与发送不太同样;
2.2 弊端
- Guest OS去操做寄存器的时候,会触发VM退出,涉及到KVM和Qemu的处理,并最终再次进入VM,overhead较大;
- 不论是在Host仍是Guest中,中断处理的开销也很大,中断涉及的寄存器访问也较多;
- 软件模拟的方案,吞吐量性能也比较低,时延较大;
因此,让咱们大声喊出本文的主角吧!frontend
3. 网卡半虚拟化
在进入主题前,先思考几个问题:异步
- 全虚拟化下Guest能够重用驱动、网络协议栈等,可是在软件全模拟的状况下,咱们是否真的须要去访问寄存器吗(好比中断处理),真的须要模拟网卡的自协商机制以及EEPROM等功能吗?
- 是否真的须要模拟大量的硬件控制寄存器,而这些寄存器在软件看来毫无心义?
- 是否真的须要生产者/消费者模型的通知机制(寄存器访问、中断)?
3.1 virtio
网卡的工做过程是一个生产者消费者模型,可是在前文中能够看出,在全虚拟化状态下存在一些弊端,一个更好的生产者消费者模型应该遵循如下原则:工具
- 寄存器只被生产者使用去通知消费者
ring-buffer
有数据(消费者能够继续消费),而再也不被用做存储状态信息;
- 中断被消费者用来通知生产者
ring-buffer
是非满状态(生产者能够继续生产);
- 生产者和消费者的状态信息应该存储在内存中,这样读取状态信息时不须要VM退出,减小overhead;
- 生产者和消费者跑在不一样的线程中,能够并行运行,而且尽量多的处理任务;
- 非必要状况下,相互之间的通知应该避免使用;
- 忙等待(好比轮询)不是一个能够接受的通用解决方案;
基于上述原则,咱们来看看从特殊到通常的过程:

- 第一行是针对网卡的实现,第二行更进一步的抽象,第三行是通用的解决方案了,对I/O操做的虚拟化通用支持;
因此,在virtio的方案下,网卡的虚拟化看上去就是下边这个样子了:

- Hypervisor和Guest都须要实现virtio,这也就意味着Guest的设备驱动知道本身自己运行在VM中;
- virtio的目标是高性能的设备虚拟化,已经造成了规范来定义标准的消息传递API,用于驱动和Hypervisor之间的传递,不一样的驱动和前端可使用相同的API;
- virtio驱动(好比图中的virtio-net driver)的工做是将OS-specific的消息转换成virtio格式的消息,而对端(virtio-net frontend)则是作相反的工做;
virtio的数据传递使用scatter-gather list(sg-list)
:

- sg-list是概念上的(物理)地址和长度对的链表,一般做为数组来实现;
- 每一个sg-list描述一个多块的buffer,消费者用它来做为输入或输出操做;
virtio的核心是virtqueue(VQ)
的抽象:
- VQ是队列,sg-list会被Guest的驱动放置到VQ中,以供Hypervisor来消费;
- 输出sg-list用于向Hypervisor来发送数据,而输入sg-list用于接收Hypervisor的数据;
- 驱动可使用一个或多个
virqueue
;

- 当Guest的驱动产生一个sg-list时,调用
add_buf(SG, Token)
入列;
- Hypervisor进行出列操做,并消费sg-list,并将sg-list push回去;
- Guest经过
get_buf()
进行清理工做;
上图说的是数据流方向,那么事件的通知机制以下:

- 当Guest驱动想要Hypervisor消费sg-list时,经过VQ的
kick
来进行通知;
- 当Hypervisor通知Guest驱动已经消费完了,经过
interupt
来进行通知;
大致的数据流和控制流讲完了,细节实现后续再跟进了。
3.2 半虚拟化方案
那么,半虚拟化框架下的网卡虚拟化数据流是啥样的呢?


相信你应该对virtio有个大概的了解了,好了,收工。
参考
《Virtio networking: A case study of I/O paravirtualization》
《 PCI/PCI-X Family of Gigabit Ethernet Controllers Software Developer's Manual》
欢迎关注我的公众号,不按期更新Linux相关技术文章。
