原文信息

作者:LoyenWang

出处:https://www.cnblogs.com/LoyenWang/

公众号:LoyenWang

版权:本文版权归作者和博客园共有

转载:欢迎转载,但未经作者同意,必须保留此段声明;必须在文章中给出原文连接;否则必究法律责任

背景

  • Read the fucking source code! --By 鲁迅
  • A picture is worth a thousand words. --By 高尔基

说明:

  1. KVM版本:5.9.1
  2. QEMU版本:5.0.0
  3. 工具:Source Insight 3.5, Visio

概述

  • 从本文开始将研究一下virtio;
  • 本文会从一个网卡虚拟化的例子来引入virtio,并从大体架构上进行介绍,有个宏观的认识;
  • 细节的阐述后续的文章再跟进;

1. 网卡

1.1 网卡工作原理

先来看一下网卡的架构图(以Intel的82540为例):

  • OSI模型,将网络通信中的数据流划分为7层,最底下两层为物理层和数据链路层,对应到网卡上就是PHYMAC控制器
  • PHY:对应物理层,负责通信设备与网络媒介(网线)之间的互通,它定义传输的光电信号、线路状态等;
  • MAC控制器:对应数据链路层,负责网络寻址、错误侦测和改错等;
  • PHYMAC通过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,分别代表RXTXring-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,前端与后端通信,后端再与底层通信,我们来分别看看发送和接收处理的流程;
  • 发送:

    1. Guest OS在准备好网络包数据以及描述符资源后,通过写TDT寄存器,触发VM的异常退出,由KVM模块接管;
    2. KVM模块返回到Qemu后,Qemu会检查VM退出的原因,比如检查到e1000寄存器访问出错,因而触发e1000前端工作;
    3. Qemu能访问Guest OS中的地址内容,因而e1000前端能获取到Guest OS内存中的网络包数据,发送给后端,后端再将网络包数据发送给TUN/TAP驱动,其中TUN/TAP为虚拟网络设备;
    4. 数据发送完成后,除了更新ring-buffer的指针及描述符状态信息外,KVM模块会模拟TX中断;
    5. 当再次进入VM时,Guest OS看到的是数据已经发送完毕,同时还需要进行中断处理;
    6. Guest OS跑在vCPU线程中,发送数据时相当于会打算它的执行,直到处理完后再恢复回来,也就是一个严格的同步处理过程;
  • 接收:

    1. 当TUN/TAP有网络包数据时,可以通过读取TAP文件描述符来获取;
    2. Qemu中的I/O线程会被唤醒并触发后端处理,并将数据发送给e1000前端
    3. e1000前端将数据拷贝到Guest OS的物理内存中,并模拟RX中断,触发VM的退出,并由KVM模块接管;
    4. KVM模块返回到Qemu中进行处理后,并最终重新进入Guest OS的执行中断处理;
    5. 由于有I/O线程来处理接收,能与vCPU线程做到并行处理,这一点与发送不太一样;

2.2 弊端

  • Guest OS去操作寄存器的时候,会触发VM退出,涉及到KVM和Qemu的处理,并最终再次进入VM,overhead较大;
  • 不管是在Host还是Guest中,中断处理的开销也很大,中断涉及的寄存器访问也较多;
  • 软件模拟的方案,吞吐量性能也比较低,时延较大;

所以,让我们大声喊出本文的主角吧!

3. 网卡半虚拟化

在进入主题前,先思考几个问题:

  1. 全虚拟化下Guest可以重用驱动、网络协议栈等,但是在软件全模拟的情况下,我们是否真的需要去访问寄存器吗(比如中断处理),真的需要模拟网卡的自协商机制以及EEPROM等功能吗?
  2. 是否真的需要模拟大量的硬件控制寄存器,而这些寄存器在软件看来毫无意义?
  3. 是否真的需要生产者/消费者模型的通知机制(寄存器访问、中断)?

3.1 virtio

网卡的工作过程是一个生产者消费者模型,但是在前文中可以看出,在全虚拟化状态下存在一些弊端,一个更好的生产者消费者模型应该遵循以下原则:

  1. 寄存器只被生产者使用去通知消费者ring-buffer有数据(消费者可以继续消费),而不再被用作存储状态信息;
  2. 中断被消费者用来通知生产者ring-buffer是非满状态(生产者可以继续生产);
  3. 生产者和消费者的状态信息应该存储在内存中,这样读取状态信息时不需要VM退出,减少overhead;
  4. 生产者和消费者跑在不同的线程中,可以并行运行,并且尽可能多的处理任务;
  5. 非必要情况下,相互之间的通知应该避免使用;
  6. 忙等待(比如轮询)不是一个可以接受的通用解决方案;

基于上述原则,我们来看看从特殊到一般的过程:

  • 第一行是针对网卡的实现,第二行更进一步的抽象,第三行是通用的解决方案了,对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

  1. 当Guest的驱动产生一个sg-list时,调用add_buf(SG, Token)入列;
  2. Hypervisor进行出列操作,并消费sg-list,并将sg-list push回去;
  3. 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相关技术文章。

【转载】Linux虚拟化KVM-Qemu分析(八)之virtio初探的更多相关文章

  1. [转载]Linux 线程实现机制分析

    本文转自http://www.ibm.com/developerworks/cn/linux/kernel/l-thread/ 支持原创.尊重原创,分享知识! 自从多线程编程的概念出现在 Linux ...

  2. KVM/QEMU/qemu-kvm/libvirt 概念全解

    目录 目录 前言 KVM QEMU KVM 与 QEMU qemu-kvm Libvirt Libvirt 在 OpenStack 中的应用 前言 如果是刚开始接触虚拟机技术的话, 对上述的概念肯定会 ...

  3. 【原创】Linux虚拟化KVM-Qemu分析(三)之KVM源码(1)

    背景 Read the fucking source code! --By 鲁迅 A picture is worth a thousand words. --By 高尔基 说明: KVM版本:5.9 ...

  4. Linux虚拟化技术KVM、QEMU与libvirt的关系(转)

    说明:个人理解,KVM是内核虚拟化技术,而内核是不能使用在界面上使用的,那么此时QEMU提供了用户级别的使用界面,相互辅助.当然,单独使用QEMU也是可以实现一整套虚拟机,不过QEMU+KVM基本是标 ...

  5. 关于Linux虚拟化技术KVM的科普 科普三(From OenHan)

    http://oenhan.com/archives,包括<KVM源代码分析1:基本工作原理>.<KVM源代码分析2:虚拟机的创建与运行>.<KVM源代码分析3:CPU虚 ...

  6. 关于Linux虚拟化技术KVM的科普 科普二(KVM虚拟机代码揭秘)

    代码分析文章<KVM虚拟机代码揭秘--QEMU代码结构分析>.<KVM虚拟机代码揭秘--中断虚拟化>.<KVM虚拟机代码揭秘--设备IO虚拟化>.<KVM虚拟 ...

  7. KVM+QEMU虚拟化概念

    概念: KVM,即Kernel-basedvirtual machine,由redhat开发,是一种开源.免费的虚拟化技术.对企业来说,是一种可选的虚拟化解决方案. 定义:基于Linux内核的虚拟机 ...

  8. 【原创】Linux虚拟化KVM-Qemu分析(一)

    背景 Read the fucking source code! --By 鲁迅 A picture is worth a thousand words. --By 高尔基 说明: KVM版本:5.9 ...

  9. 【原创】Linux虚拟化KVM-Qemu分析(四)之CPU虚拟化(2)

    背景 Read the fucking source code! --By 鲁迅 A picture is worth a thousand words. --By 高尔基 说明: KVM版本:5.9 ...

  10. 【原创】Linux虚拟化KVM-Qemu分析(五)之内存虚拟化

    背景 Read the fucking source code! --By 鲁迅 A picture is worth a thousand words. --By 高尔基 说明: KVM版本:5.9 ...

随机推荐

  1. Rust中的Copy和Clone

    1.Copy和Clone Rust中的Copy和Clonetrait都允许创建类型实例的副本.它们都提供了一种复制类型实例的方法,但它们之间存在一些重要的区别.了解这些区别有助更好地使用这两个特征. ...

  2. 记一次 .NET 某外贸ERP 内存暴涨分析

    一:背景 1. 讲故事 上周有位朋友找到我,说他的 API 被多次调用后出现了内存暴涨,让我帮忙看下是怎么回事?看样子是有些担心,但也不是特别担心,那既然找到我,就给他分析一下吧. 二:WinDbg ...

  3. 再解 [NOI2017] 整数

    提供一个来自 CF 大佬 adament 的有趣思路. 首先我们知道的是一个只增加的 \(b\) 进制整数计数器,如果 \(b\) 是常数那么复杂度是均摊 \(O(1)\) 的.证明只需要考虑将 \( ...

  4. 【Azure Developer】Azure AD 注册应用的 OAuth 2.0 v2 终结点获取的 Token 解析出来依旧为v1.0, 这是什么情况!

    问题描述 使用 Azure AD 注册应用 Oauth2 v2.0的终结点(OAuth 2.0 token endpoint (v2):https://login.partner.microsofto ...

  5. C# 实现窗体启动时隐藏

    在某些时候需要实现一个界面的后台程序,程序自动运行,但起初不显示窗体,在满足触发条件时显示,此时需要在运行程序时先自动隐藏窗体. 修改窗体对应的Program.cs: using System; us ...

  6. python Unitest和pytest 介绍和安装

    前言 目前有两种纯测试的测试框架,pytest和unittest,这系列文章主要介绍pytest为主 UnitTest测试框架理论 python 自带的单元测试框架,常用在单元测试 在自动化测试中提供 ...

  7. MySql的数据存储之B+树(浅谈)

    一.MySql的实际存储位置 B+树是MySql数据结构的主流存储方式,包括InnoDB和MYISAM引擎,它们的默认存储结构都是B+树 了解B+树前,我们先要知道MySql 的实际存储位置在哪? 有 ...

  8. SICP:元循环求值器(Python实现)

    求值器完整实现代码我已经上传到了GitHub仓库:TinySCM,感兴趣的童鞋可以前往查看.这里顺便强烈推荐UC Berkeley的同名课程CS 61A. 在这个层次结构的最底层是对象语言.对象语言只 ...

  9. Github Copilot Chat 初体验

    最近因为阳了的缘故一直躺在床上.今天终于从床上爬起来了.不是因为好透了,而是因为我收到了申请Copilot Chat preview 权限通过的邮件.实在忍不住,于是起床开电脑在咳嗽声中进行了一番体验 ...

  10. 用好kafka,你不得不知的那些工具

    前言 工欲善其事,必先利其器.本文主要分享一下消息中间件kafka安装部署的过程,以及我平时在工作中针对kafka用的一些客户端工具和监控工具. kafka部署架构 一个kafka集群由多个kafka ...