当前位置 博文首页 > LoyenWang:【原创】Linux虚拟化KVM-Qemu分析(八)之virtio初探
Read the fucking source code!
--By 鲁迅A picture is worth a thousand words.
--By 高尔基说明:
先来看一下网卡的架构图(以Intel的82540为例):
PHY
和MAC控制器
;PHY
:对应物理层,负责通信设备与网络媒介(网线)之间的互通,它定义传输的光电信号、线路状态等;MAC控制器
:对应数据链路层,负责网络寻址、错误侦测和改错等;PHY
和MAC
通过MII/GMII(Media Independent Interface)
和MDIO(Management Data Input/output)
相连;MII/GMII(Gigabit MII)
:由IEEE
定义的以太网行业标准,与媒介无关,包含数据接口和管理接口,用于网络数据传输;MDIO
接口,也是由IEEE
定义,一种简单的串行接口,通常用于控制收发器,并收集状态信息等;我们主要关心它的数据流,所以,看看它的工作原理吧:
生产者-消费者
模型,简单来说,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
也就能转起来了;在网卡数据流图中,我们也基本看到了网卡驱动的影子,驱动与网卡之间是异步通信:
ring-buffer
的创建及初始化;ndo_start_xmit
负责将网络包通过驱动程序发送出去,netif_receive_skb
负责通过驱动程序接收网络包数据;struct sk_buff
来存储;全虚拟化方案,通过软件来模拟网卡,Qemu+KVM的方案如下图:
e1000
,前端与后端通信,后端再与底层通信,我们来分别看看发送和接收处理的流程;
发送:
TDT
寄存器,触发VM的异常退出,由KVM模块接管;e1000
寄存器访问出错,因而触发e1000前端
工作;e1000前端
能获取到Guest OS内存中的网络包数据,发送给后端,后端再将网络包数据发送给TUN/TAP驱动,其中TUN/TAP为虚拟网络设备;ring-buffer
的指针及描述符状态信息外,KVM模块会模拟TX中断;接收:
e1000前端
;e1000
前端将数据拷贝到Guest OS的物理内存中,并模拟RX中断,触发VM的退出,并由KVM模块接管;所以,让我们大声喊出本文的主角吧!
在进入主题前,先思考几个问题:
网卡的工作过程是一个生产者消费者模型,但是在前文中可以看出,在全虚拟化状态下存在一些弊端,一个更好的生产者消费者模型应该遵循以下原则:
ring-buffer
有数据(消费者可以继续消费),而不再被用作存储状态信息;ring-buffer
是非满状态(生产者可以继续生产);基于上述原则,我们来看看从特殊到一般的过程:
所以,在virtio的方案下,网卡的虚拟化看上去就是下边这个样子了:
virtio的数据传递使用scatter-gather list(sg-list)
:
virtio的核心是virtqueue(VQ)
的抽象:
virqueue
;add_buf(SG, Token)
入列;get_buf()
进行清理工作;上图说的是数据流方向,那么事件的通知机制如下:
kick
来进行通知;interupt
来进行通知;大体的数据流和控制流讲完了,细节实现后续再跟进了。
那么,半虚拟化框架下的网卡虚拟化数据流是啥样的呢?
相信你应该对virtio有个大概的了解了,好了,收工。
《Virtio networking: A case study of I/O paravirtualization》
《 PCI/PCI-X Family of Gigabit Ethernet Controllers Software Developer's Manual》
欢迎关注个人公众号,不定期更新Linux相关技术文章。